방법들

Data.init(contentsOf url: URL)

기본적으로 네트워크를 통해 이미지를 가져오기 위해 사용했던 첫번째 방식이었다.

그 당시, DispatchQueue를 사용하여 UI는 Main Thread에서 변경했지만, 이미지를 가져오기위한 작업은 저 Data(contentsOf:)를 사용했다.

간단한 코드였으며, CollectionView나 TableView처럼 많은 이미지를 가져온다거나 이미지의 크기가 크지 않기에 앱이 죽지 않고 돌아갔지 싶다.

 

쩃든, 네트워크 통신을 통한 파일/이미지를 받을 때는 해당 방법을 사용하면 안된다.

Important
Don't use this synchronous initializer to request network-based URLs. For network-based URLs, this method can block the current thread for tens of seconds on a slow network, resulting in a poor user experience, and in iOS, may cause your app to be terminated.
Instead, for non-file URLs, consider using the dataTask(with:completionHandler:) method of the URLSession class. See Fetching Website Data into Memory for an example.

첫줄에서도 나와있다시피 이 init은 synchronous하다 -> 동기방식으로 동작한다.

만약, 해당 코드를 Main Thread에서 수행한다면, 응답이 올때까지 Thread는 멈추게된다. (안드에서는 이러한 상황을 ANR이라 했던 것 같은데, iOS에서는 어떻게 부르는지 모르겠다)

따라서 아래에 적혀있는 것처럼 URLSession Class에 있는 dataTask 메소드 사용을 권장한다.

이미지를 다운로드하는 상황에는 downloadTask도 있다.

 

URLSession - downloadTask

  • HTTP / HTTPS 를 통해 데이터를 주고받기 위해 API를 제공해주는 클래스

Session 과 Task 등 자세한 이야기는 이후의 포스팅에서 다루겠다.

 

downloadTask

서버로부터 데이터를 다운로드 받아서 파일의 형태로 저장한다.

 

1. URL을 사용하는 방법 -> URL을 알고 있는 상황에서 사용

2. URLRequest를 사용하는 방법-> Query, HTTP메소드, 헤더 등 설정이 필요한 경우 사용

 

//4개의 메소드가 존재
downloadTask(with url: URL) -> URLSessionDownloadTask

downloadTask(with url: URL, completionHandler: @escaping(URL?, URLResponse?, Error?) -> Void)

downloadTask(with request: URLRequest) -> URLSessionDownloadTask

downloadTask(with request: URLRequest, completionHandler: @escaping(URL?, URLResponse?, Error?) -> Void)

 

URLSession.shared.downloadTask(with: imageURL){ url, response, error in
	.........
    
    if let url = url{
    	let image = UIImage(contentsOfFile: url){
        	DisptchQueue.main.async{
            	......
            }
        }
    }
    
    .........
}.resume()

 

위와 같은 코드로 동작시킬 수 있다.

이미지를 계속해서 불러오는 것은 비효율적일 수 있다. 따라서 Cache(캐시)를 사용할 수 있는데

FileManager를 사용하면 캐시를 구현할 수 있다.

 

Kingfisher (https://github.com/onevcat/Kingfisher)

캐시 전략, 코드의 길이, 사용법 등 번거롭고 귀찮음을 해결하기 위해 나온 라이브러리이다.

 

  • 비동기 이미지 다운로드 및 캐싱을 위해 사용
  • URLSession 기반이기 떄문에, 네크워킹 및 로컬 데이터도 로드 가능
  • 유용한 이미지 프로세서 및 필터가 제공됨
  • 메모리와 디스크의 계층 하이브리드 캐시
  • 성능 향상을 위해 이전에 다운로드한 콘텐츠를 취소 가능, 재사용 가능
  • Indicator
  • SwiftUI 지원
  • GIF 형식 지원

 

//정말 쉬웠다

imgView.kf.setImage(with: imageURL)
//끝...
//비동기로 동작한다

 

오늘 처음 써보며... 오픈소스의 대단함을 새로 느꼈다. 이미지를 불러오는 데에 조금씩 버벅이길래 성능 향상을 위해 Disk에 Cache도 구현하고 CollectionView의 Prefetching도 사용해보았지만 눈에 띄게 향상되진 않았었다.

그런데 KingFisher를 사용하니 눈에 띄게 성능이 향상된 것을 볼 수 있었다. -> 코드를 까보면서 뭐가 다른건지 확인해봐야겠다

 

Processor

이미지를 좀더 유연하게 만들어주는 역할을 한다.

setImage의 매개변수에는 Resource, Placeholder, options, progressBlock, completionHandler 등이 있다.

이중 options에 해당하며 없다면 default를 사용한다고 한다.

 

  • cornerRadius
  • Resizing
  • Blur
  • TintColor
  • Sampling

등의 기능을 제공하는 프로세서들을 사용할 수 있다.

 

또한, 싱글톤 객체를 이용해서 함수를 만들 수도 있다.

func downloadImage(with url: URL){
	let resource = ImageResource(downloadURL: url)
    
    KingfisherManager.shared.retrieveImage(with: resource){ result in
    	
        switch result{
        case .success(let value):
	        print("Image : \(value.image)")
        case .failure(let error):
        	print(error)
        }
    }
}

 

Cache

KingFisher가 알아서 메모리와 디스크에 캐시를 하고 전략 또한 정해져있는 것으로 알고 있다. -> (각 애플리케이션의 구조에 따라 캐시전략이 다를 수도 있는데, 이건 더 알아봐야겠다)

위에서 precessor를 넣을 수 있었던 options에 .cacheMemoryOnly 를 넣어주면 메모리캐싱만 수행하기된다. -> 메모리에만 캐싱하면 빠르긴하겠지만.... 굳이 좋을까...? 궁금증이 든다

캐시는 메소드를 통해 지울 수 있다.

 

 

이후에 보면 좋은 글 : 1consumption.github.io/posts/about-kingfisher(1)/

 

 

오토레이아웃

해당 뷰에 적용된 제약조건에 따라 뷰 계층 구조에 있는 모든 뷰의 크기와 위치를 동적으로 계산한다.

 

외부변경

SuperView의 크기나 모양이 변경되면 외부변경이 발생한다. 변경할 때마다 사용 가능한 공간을 최대한 활용하도록 레이아웃을 업데이트해야 한다.

 

내부변경

View의 크기 or UI 조작시 발생한다.

 

속성

SafeArea

애플리케이션이 상태바, 네비게이션바, 탭바 등을 가릴 수 없는 애플리케이션만의 영역이다.

iPhoneX에서부터 Notch(노치)가 생겨나며 생겨났다.

최대한 SafeArea를 이용해야 하며, SuperView를 사용하지 않는 편이 좋다. (동영상에서는 사용하는 것 같기는 한데, 가로모드와 세로모드의 차이때문인지 가이드가 다른 건지는 확실히 모르겠다...)

 

Constraint

뷰와 뷰 사이의 관계(하나의 뷰는 여러개의 뷰와 관계를 생성할 수 있다)를 정의하는 것이다.

위치는 방적식으로 나타낸다.

속성 설명
Width 너비
Height 높이
Top 상단
Bottom 하단
Baseline 텍스트 하단
Leading 텍스트 방향 시작
Trailing 텍스트 방향 끝
CenterX 수평 중심
CenterY 수직 중심

 

제약의 방정식 :

A.Leading = 1.0 * B.Trailing + 12.0 -> A의 Leading(텍스트방향의 시작위치)는 B의 Trailing(텍스트 방향의 끝위치)의 1.0배에 12.0을 더한 위치 -> (Interface Builder로 직접해보면 감이 쉽게 온다...)

 

Intrinsic Content Size

뷰가 원래 가지고 있었던 크기

 

Priority Constraint

제약 사이의 우선도

  • hugging Priority
    • 우선 순위(가로/세로)가 겹칠 때(정확한 판단이 어려울 때) 높은 우선순위의 Constraint를 적용
  • Compression Priority
    • 낮은 우선순위를 가진 제약(뷰)를 밀면서까지 높은 우선순위를 가진 제약을 지킴

Margin

레이아웃에 사용되는 간격

 

 

구현 방식

Programmatically

코드를 사용하여 AutoLayout을 적용한다.

iOS를 공부하는 초반에는 굉장히 어렵다. -> 내가 적용한 제약조건들이 적용되었을 때, 어떤 모양을 가질 지 상상이 가지 않기 때문

(분명 나는 사자를 그렸는데, 꼬리, 머리, 다리 위치가 뒤죽박죽인 상황...? 혹은 아예 없어지는 상황?)

 

하지만, 장점도 존재한다.

IB(Interface Builder)를 사용하는 것보다 훨씬 가독성이 좋아 협업에 적합하다.

-> IB로 제약사항을 작성 시, 변경/유지보수할 때, 하나하나 확인해야하며 굉장히 불편하다 즉, 자신 외에는 알아보는 데에 오래걸릴 수 있다.

(같은 View를 그리기 위해 다양한 방법이 존재하기 때문, 자신도 며칠 지나면 못 알아봄...)

BUT, 코드로 작성한다면, 누가 봐도 IB보다는 쉽고 빠르게 이해할 수 있을 것이다.

 

IB(Interface Builder)

XCode내에 내장된 IB를 사용하여 AutoLayout을 적용한다.

iOS를 공부하는 초반에 권장(이후에도 IB를 사용하여 개발을 진행한다고도 한다) -> 내가 적용한 제약조건들이 적용되는 것을 실시간으로 볼 수 있기 때문에, 보다 쉽게 View를 그릴 수 있다.

또한 IBOutlet, IBAction등을 사용하여 쉽게 UI Components와 Event를 동작/구현할 수 있음

 

 

UIViewController

역할

  • 데이터의 변경에 대한 응답을 View에 Update한다.
  • View와 User와의 상호 작용에 응답한다.
  • View의 크기 조정 및 Layout 관리
  • 다른 객체(viewController 포함)를 조정한다.

간략하게, View와 밀접하게 연결되어 있다.

class UIViewController : UIResponder

또한, UIViewController는 UIResponder를 상속받았다. UIResponder에 대한 이야기는 밑에서 다시 할 것이다.

우선은 SuperView/SubViews 들과의 응답자 체인으로 엮여있다.
이것은 즉

1. 자신의 event의 처리를 SuperView에 passing할 수 있다.

2. 자신의 SubView들의 event를 자신이 처리할 수 있다.

가 가능하다.

 

대부분의 애플리케이션 내에는 여러개의 ViewController가 존재하고 사용된다.

어떤 VC(ViewController)는 리스트를 나타내는 테이블뷰 하나만을 관리할 수 있고,

다른 VC는 리스트, 회원창, 친구목록 등 다양한 View를 관리할 수 있다.

 

UIResponder

앞선 블로그 코코아 프레임워크에서 말했듯, 이 또한 클래스로 NSObject를 상속받는다.

class UIResponder : NSObject
  • Event 처리에 중요한 역할을 한다.
  • 여러 메소드와 next라는 프로퍼티를 사용하여 부모에게 event를 전달하거나, 동시 처리도 가능해진다.
    • touchesBegan
    • touchesMoved
    • touchesEnded
    • touchesCancelled
  • next라는 프로퍼티를 사용하여 다음 responder에게 이벤트를 전달 가능

responder 라고는 주로 becomeFirstResponder() / resignFirstResponder() 를 사용한 기억이 많다.

TextField를 사용하며 키보드를 올리고 내리고 하기 위해 사용해왔지만, 정확히 어떻게 동작하는 지에 대해서는 생각해보질 못했다.

키보드가 올라오기 위해서는 TextField가 window의 첫번째 Responder가 되어야 한다.

메소드이름 그대로 TextField가 첫번째 Responder가 되기 때문에, 키보드가 올라오며, 반대로 Resign하면 내려간다.

 

View 관리

  • 각 VC는 View들이 포함되는 RootView를 관리한다.
  • RootView는 주로 나머지 뷰 계층 구조에서 Container 역할을 한다.
  • VC의 View들은 lazy하게 메모리에 Load된다.
    • 선언한다고 메모리에 적재되는 것이 아닌, 사용할 때(즉, 호출될 때) 메모리에 적재된다.
    • VC 생성 시의 생명주기

StoryBoard를 사용할 때

스토리보드 내에서 1. VC로 사용할 View 와 2. VC 를 지정(연결)한다.

주로 간단한 화면 구조의 애플리케이션이나 혼자서 작업할 때 사용되는 방법이다.

// 스토리보드를 통한 VC 초기화
let newVC = self.storyboard?.instantiateViewController(withIdentifier: "VC이름")

 

Nib  파일을 사용할 때

Nib파일을 사용하여 VC에 대한 View를 지정한다.

스토리보드를 사용하지 않기에, 스토리보드 내에서 가능했던 VC간의 Segue나 관계를 사용/정의할 수 없다.

초기화 방법 : init(nibName:bundle:)을 사용해서 초기화한다.

 

이후의 View들의 Load

VC의 생명주기 메소드들 중 가장 첫 단계인 loadView() 메소드를 사용하여 나열된다.

이후에 DidLoad, willAppear, DidAppear ... 

 

VC의 크기(RootView의 크기)

VC의 RootView는 항상 할당된 공간에 크기가 맞춰진다. 또한 RootView 내에 있는 SubView들은 Interface Builder를 사용하여 각 뷰와 SuperView와의 관계를 통해 크기, 위치 등이 지정된다.

 

구현 방식

1. AutoLayout - View 사이의 관계, 제약조건 등을 통하여 크기, 위치 등을 설정하며, 기기 크기에 대해 대응 가능

2. Frame - CGSize(크기), CGRect(위치)를 직접 할당한다.

 

 

View 관련 Notification

위에 사진으로 나타낸 VC의 생명주기의 메소드이름을 통해 View들이 어떤 상태인지를 알 수 있다.

반대로 말하면, View들의 현재 상태를 VC를 통해 알 수 있다.

 

UIKit

iOS를 공부하고 진행하다보면 코코아(Cocoa) 라는 단어를 여러번 접하게 된다.

코코아 터치, 코코아 프레임워크 등...

 

Cocoa

- NSObject를 상속받는 모든 클래스를 가르키는 단어

 

Cocoa Touch

- 코코아 터치 프레임워크란 iOS 환경을 구축하기 위한 최상위 프레임워크이다.

- 일반적으로, 옵씨에서의 NSObject 와 스위프트에서 사용되는 UIKit / Foundation 등에 포함되는 대부분의 클래스가 여기에 속한다.

  - 따라서 iOS 애플리케이션은 코코아 터치 프레임워크 환경에서 구축된다고 할 수 있다.

UIKit

- UIKit 프레임워크는 UI를 관리하고, 사용자와의 이벤트(Interaction)을 처리해주는 프레임워크다.

  - macOS에서는 AppKit이라는 프레임워크를 사용한다고 한다.

- 처리하는 주된 이벤트들은 제스처(터치, 스와이프, 핀치... 등), 애니메이션, 이미지, 텍스트 등 말 그대로 UI와 연결되는 것들이다.

- UIView, UIViewController 등의 앞에 UI가 붙는 클래스들 또한 UI를 위해 사용되는 것이고 당연히 UIKit 프레임워크에 속한다.

 

Foundation

- 애플리케이션의 중심을 담당한다.

- 원시 데이터 타입(String, Int, Double 등)이 Foundation 프레임워크에 포함되어있기 때문에, 반드시 사용된다.

- Resource, Notification, Error, FileSystem, URL 등 애플리케이션을 동작하기 위해 필요한 대부분이 속해있다.

 

 

그렇다면 UI를 작성하기 위해 UIKit을 import하고 동작을 위해 Foundation을 import해야 하는가??

기본적인 코코아 프레임워크의 계층구조이다.

UIKit은 최상단의 Cocoa Touch 계층이고 Foundation은 CoreServices 계층에 속한다.

그렇기 때문에, UIKit을 import하면 Foundation을 별도로 import하지 않아도 된다.

+ Recent posts