MVC 디자인패턴 중 Controller에 속하기 때문에 화면에 보이진 않으나 탭바를 통해 탭 컨트롤을 할 수 있도록 제공하며, 자신이 관리하는 뷰 컨트롤러의 뷰를 자신의 실질적인 뷰로 사용한다.
NavigationController
는 자신이 관리하는 ViewController 목록을 ViewControllers라는 프로퍼티로 갖고 있는데,
TabBarController
도 마찬가지로 자신이 관리하는 ViewController 목록을 ViewControllers로 갖고 있다.
이런 경우,
TabBarController
는 NavigationController 두 개를 ViewControllers라는 프로퍼티로 갖고 있으며, 각각의 NavigationController
도 두 개의 View Controller를 ViewControllers라는 프로퍼티로 갖고 있다.
윈도우 위에,
TabBarController
위에, NavigationController
위에 실질적인 뷰가 쌓여 최종 뷰를 만든다.
이렇게 TabBarController
는 단일로 쓰이는 경우보다 NavigationController
와 결합하여 쓰이는 경우가 많다. NavigationController
가 정보의 흐름을 위해 사용된다면 TabBarController
는 정보의 분류를 위해 사용된다.
TabBarController
는 이 탭바를 사용해 유저에게 선택지를 제공한다. 탭바는 UITabBar
클래스를 기본클래스로 사용하고, 각각의 탭은 UITabBarItem
클래스를 기본클래스로 사용하는 아이템들을 표현한다.
아이템에 badge value라는 프로퍼티 값을 변경해주면 이렇게 뱃지를 띄울 수 있다.
아이템은 탭바나 TabBarController
가 관리하는 것이 아니라 뷰컨트롤러가 스스로 관리한다.
네비게이션 아이템을 NavigationController
가 아닌 뷰컨트롤러가 관리하는 것과 비슷!
스토리보드가 생기기 전에는 인터페이스를 구성할 때 Xib(XML Interface Builder)
를 사용했고, Xib가 생기기 전에는 Nib(NeXT Interface Builder)
를 사용했다.
Nib에서 Xib로 변경된 이유는 SCM(Source Code Management) 때문!
Nib
은 binary file이라 코드의 변경과 추적이 어려웠음Xib
는 xml 기반이라 코드의 변경이력을 확인하기 수월Xib 파일은 빌드하면 Nib 파일이 된다.
이 Nib 파일을 그럼 어떻게 사용할까? 코드를 보자.
let cellNib: UINib = UINib.init(nibName: "FriendTableViewCell", bundle: nil)
뷰컨트롤러에서 셀의 인터페이스를 불러오는 코드다.
FriendTableViewCell.xib 파일이 빌드되면서 Nib파일이 되었으므로 nibName에 이름을 그대로 넣어줄 수 있는 것.
UINib 클래스를 통해 이 Nib 파일을 아카이브 해제해서 셀 인터페이스를 메모리에 올려둔다.
self.tableView.register(cellNib, forCellReuseIdentifier: "friendCell")
테이블뷰에서 이 셀을 사용하려면 register 메서드로 등록을 해주어야 한다.
셀을 재사용큐에서 빼오기 전에 이 메서드를 호출하여 테이블뷰에게 그 셀을 찾아오는 방법을 알려주는 것이다. 지정된 유형의 셀이 현재 재사용 큐에 없는 경우, 테이블뷰는 제공된 정보를 사용하여 새 셀을 자동으로 작성한다.
let session: URLSession = URLSession(configuration: URLSessionConfiguration.default)
URL을 만들어 네트워크 통신을 할 때 세션이 필요하고, 그 세션의 역할을 하는 것이 URLSession
의 인스턴스다.
URLSessionConfiguration
은 프로퍼티를 지정함에 따라 URL 유형을 달리할 수 있다.
let session: URLSession = URLSession.shared
네트워크 통신을 요청할 때마다 매번 새로운 세션을 생성할 수도 있지만 싱글턴 인스턴스를 사용해도 된다. shared 프로퍼티를 사용하면 싱글턴 인스턴스를 불러올 수 있다.
URLSession은 여러 개의 URLSessionTask를 만들 수 있다. 이 URLSessionTask는 여러 작업의 모체가 되는 부모클래스고, 이 클래스를 상속받는 세 가지의 클래스를 통해 실제 서버와의 통신을 한다.
URLSessionTask
는 작업에 따라 세 가지 종류가 있다.
func dataTask(with url: URL,
completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionDataTask
URLSession과 URLSessionTask는 모두 Delegate 프로토콜을 채택한다.
Delegate를 통해 업로드나 다운로드의 진행률과 같은 네트워크 중간 과정을 파악할 수 있고, 데이터 수신 결과를 URLSessionTask의 핸들러로 처리하는 대신 Delegate를 사용해 처리할 수도 있다.
URLSession은 DATA, FILE, FTP, HTTP, HTTPS와 같은 여러 네트워킹 프로토콜을 지원한다.
이 중 HTTP 프로토콜을 사용할 때는 꼭 알아두어야 할 것이 있다.
iOS 9.0버전부터 적용
ATS가 활성화되어있는 상태에서는 HTTP 통신을 할 수가 없다. 애플이 제시한 최소 보안기준ex. TLS 1.2 이상을 지원하는 서버, 2048bit 이상의 RSA 키 또는 256bit 이상의 ECC 키가 있는 SHA256 인증서 등
을 충족해야 통신을 할 수 있는데, HTTP 통신은 암호화되어있지 않은 통신이기 때문!
부득이하게 보안기준을 갖추지 못했다 해도 예외적으로 허용해주는 경우도 있다.
ATS를 비활성화하는 방법도 있다. 프로젝트의 info.plist 파일에 해당 키를 등록해주면 된다.
GCD(Grand Central Dispatch)는 애플이 멀티코어/멀티프로세싱 환경에 최적화된 프로그램을 만들 수 있도록 개발한 기술이다. 스레드 풀을 운영체제에서 관리할 수 있도록 구현되어 있어서, 프로그래머가 스레드를 일일이 신경쓰지 않아도 된다. 프로그래머는 어떤 작업을 해야할지만 알려주면 운영체제가 알아서 스레드를 생성하고 작업을 실행해준다.
GCD에게 작업을 부여할 때 DispatchQueue를 사용한다. 주로 간단한 작업을 동기/비동기적으로 수행할 때 사용!
Serial Dispatch Queue
Concurrent Dispatch Queue
// API 요청을 담당할 구조체
struct Request {
// MARK: - Private Properties
private static let friendsURL: URL = URL(string: "https://randomuser.me/api/1.1/?inc=name,nat,cell,picture&format=json&results=50&noinfo")!
// 이미지 다운로드 디스패치 큐
private static let imageDispatchQueue: DispatchQueue = DispatchQueue(label: "image")
// 이미지 메모리 캐시를 위한 딕셔너리
private static var cachedImage: [URL: UIImage] = [:]
}
DispatchQueue를 프로퍼티로 만들어두었다. 이 때 종류를 label
은 해당 큐의 구분값으로 사용되고, label 외에도 qos
, attributes
에 값을 넘겨줄 수 있으며 위 코드처럼 값을 지정해주지 않으면 전부 default값으로 넘어간다.
중요한 건 attributes
인데, attributes에 값을 넘겨주지 않으면 이 큐는 기본적으로 Serial 큐가 된다. attributes: .concurrent
요렇게 해줘야 비로소 Concurrent 큐가 된다.
imageDispatchQueue.async {
guard let data: Data = try? Data(contentsOf: url) else {
print("데이터 - 이미지 변환 실패")
DispatchQueue.main.async {
completion(nil)
}
return
}
let image: UIImage? = UIImage(data: data)
cachedImage[url] = image
DispatchQueue.main.async {
completion(image)
}
}
async
메소드를 통해 클로저를 전달해줬다.
이 클로저가 바로 큐에 대기하다가 실행될 작업이다.
이렇게 디스패치큐를 생성하고 async 메소드를 통해 작업을 전달하면 GCD가 스스로 main 스레드가 아닌 다른 스레드에서 비동기적으로 작업을 수행한다. 우리는 스레드를 만들 필요도, 신경쓸 필요도 없는 것!
작업이 완료되면 completion(image)
를 실행한다든지 해서 UI에 결과를 업데이트해준다. UI 변경 작업은 main 스레드에서 해야 하므로 main 디스패치큐를 사용해야 한다. DispatchQueue.main
처럼 main 프로퍼티를 써서 main 디스패치큐를 불러올 수 있다. 이 main 디스패치큐에 추가된 작업은 main 스레드에서 실행된다.
모든 작업을 메인 스레드에서 하면 안 되는 이유