[TIL] 2022-04-26

선주·2022년 4월 26일
0

TIL

목록 보기
9/13

📌 UITabBarController

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

스토리보드가 생기기 전에는 인터페이스를 구성할 때 Xib(XML Interface Builder)를 사용했고, Xib가 생기기 전에는 Nib(NeXT Interface Builder)를 사용했다.

Nib에서 Xib로 변경된 이유는 SCM(Source Code Management) 때문!

  • Nib은 binary file이라 코드의 변경과 추적이 어려웠음
  • Xib는 xml 기반이라 코드의 변경이력을 확인하기 수월
    그런데 이 Xib마저도 각각의 뷰마다 interface file을 생성해주어야 했기 때문에 지금의 스토리보드보다 뷰 사이의 흐름을 파악하기 쉽지 않고, interface file이 많아지는 게 문제였음

스토리보드가 생겼어도 여전히 Xib는 사용 가능! 커스텀셀을 만들 때 외에도 자주 쓰이는 뷰를 만들어둬서 재사용이 용이하게 할 수도 있고, 단독으로 view controller interface를 구현할 수도 있는 등 다양한 용도로 사용할 수 있다.

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 메서드로 등록을 해주어야 한다.

셀을 재사용큐에서 빼오기 전에 이 메서드를 호출하여 테이블뷰에게 그 셀을 찾아오는 방법을 알려주는 것이다. 지정된 유형의 셀이 현재 재사용 큐에 없는 경우, 테이블뷰는 제공된 정보를 사용하여 새 셀을 자동으로 작성한다.




📌 URLSession

URLSessionConfiguration

let session: URLSession = URLSession(configuration: URLSessionConfiguration.default)

URL을 만들어 네트워크 통신을 할 때 세션이 필요하고, 그 세션의 역할을 하는 것이 URLSession의 인스턴스다.

URLSessionConfiguration은 프로퍼티를 지정함에 따라 URL 유형을 달리할 수 있다.

  • default: 기본 통신 할 때 사용
  • ephemeral: 쿠키나 캐시를 저장하지 않는 정책을 사용할 때 사용
  • background: 앱이 백그라운드에 있는 상태에서 컨텐츠를 업로드/다운로드할 때 사용
let session: URLSession = URLSession.shared

네트워크 통신을 요청할 때마다 매번 새로운 세션을 생성할 수도 있지만 싱글턴 인스턴스를 사용해도 된다. shared 프로퍼티를 사용하면 싱글턴 인스턴스를 불러올 수 있다.


URLSessionTask

URLSession은 여러 개의 URLSessionTask를 만들 수 있다. 이 URLSessionTask는 여러 작업의 모체가 되는 부모클래스고, 이 클래스를 상속받는 세 가지의 클래스를 통해 실제 서버와의 통신을 한다.

URLSessionTask는 작업에 따라 세 가지 종류가 있다.

  • URLSessionDataTask
    주로 짧은 시간이 걸리는 데이터를 주고받는 데 사용! 전달받은 데이터의 타입은 Data 타입이다.
    func dataTask(with url: URL,
    			  completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionDataTask
  • URLSessionUploadTask, URLSessionDownloadTask
    파일을 주고받을 때 주로 사용한다.

URLSessionDelegate

URLSession과 URLSessionTask는 모두 Delegate 프로토콜을 채택한다.
Delegate를 통해 업로드나 다운로드의 진행률과 같은 네트워크 중간 과정을 파악할 수 있고, 데이터 수신 결과를 URLSessionTask의 핸들러로 처리하는 대신 Delegate를 사용해 처리할 수도 있다.


URLSession Protocol

URLSession은 DATA, FILE, FTP, HTTP, HTTPS와 같은 여러 네트워킹 프로토콜을 지원한다.

이 중 HTTP 프로토콜을 사용할 때는 꼭 알아두어야 할 것이 있다.

ATS(App Transport Security)

iOS 9.0버전부터 적용
ATS가 활성화되어있는 상태에서는 HTTP 통신을 할 수가 없다. 애플이 제시한 최소 보안기준ex. TLS 1.2 이상을 지원하는 서버, 2048bit 이상의 RSA 키 또는 256bit 이상의 ECC 키가 있는 SHA256 인증서 등을 충족해야 통신을 할 수 있는데, HTTP 통신은 암호화되어있지 않은 통신이기 때문!

ATS Exceptions

부득이하게 보안기준을 갖추지 못했다 해도 예외적으로 허용해주는 경우도 있다.

  • AVFoundation 프레임워크를 통한 스트리밍 서비스
  • WebKit을 통한 콘텐츠 요청
  • 로컬 네트워크 연결
  • 서버를 최신 TLS 버전으로 업그레이드하지 못한 경우
    (최대한 빨리 업그레이드해서 기준을 충족해야 앱스토어에서 내려가는 불상사를 피할 수 있다.)

ATS를 비활성화하는 방법도 있다. 프로젝트의 info.plist 파일에 해당 키를 등록해주면 된다.

  • 특정 도메인의 HTTP 통신을 허용하는 것
  • 모든 HTTP 통신을 허용하는 것



📌 GCD

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 스레드에서 실행된다.


모든 작업을 메인 스레드에서 하면 안 되는 이유

  • 하나의 스레드에서 작업은 한 번에 하나씩 차례대로 진행된다. 메인 스레드에서 모든 작업을 처리하면 네트워크 작업처럼 시간이 오래 걸리는 작업이 끝날 때까지 뒤의 작업이 올스탑된다. 사용자들은 앱이 멈췄다고 생각할 것.

참고
Tistory | 참신러닝

profile
기록하는 개발자 👀

0개의 댓글

관련 채용 정보