Threading model - GCD와 비교하며

Untitled

유저가 새로운 뉴스 피드를 보고 싶다고 요청한 상황을 가정해봅시다. (새로고침 등)

Untitled

메인스레드에서 user event gesture를 핸들링합니다. 여기에서 데이터베이스 작업을 처리하는 직렬큐(SerialQueue)에 요청을 비동기식(async)으로 dispatch합니다.

그 이유는 두 가지입니다.

  1. 작업을 다른 대기열로 디스패치함으로써 잠재적으로 많은 양의 작업이 발생할 때까지 기다리는 동안에도 메인 스레드가 사용자 입력에 계속 응답할 수 있도록 합니다.
  2. 둘째, 직렬 큐는 상호 배제(mutual exclusion)를 보장하기 때문에 데이터베이스에 대한 액세스가 보호됩니다. 데이터베이스 대기열에 있는 동안 사용자가 구독한 뉴스 피드를 반복해서 살펴보고 각 피드에 대해 해당 피드의 콘텐츠를 다운로드하기 위해 URLSession에 네트워킹 요청을 예약합니다.

Untitled

Untitled

네트워킹 요청의 결과가 들어오면 동시 대기열(concurrent queue)인 delegate queue에서 URLSession 콜백이 호출됩니다. 각 결과에 대한 완료 핸들러에서 각 피드의 최신 요청으로 데이터베이스를 동기식으로 업데이트하여 나중에 사용할 수 있도록 캐시합니다.

그리고 최종적으로 UI를 업데이트 하기 위해 main thread를 깨울 것 입니다.


func deserializeArticles(from data: Data) throws -> [Article] { /* ... */ }
func updateDatabase(with articles: [Article], for feed: Feed) { /* ... */ }

let urlSession = URLSession(configuration: .default, delegate: self, delegateQueue: concurrentQueue)

for feed in feedsToUpdate {
    let dataTask = urlSession.dataTask(with: feed.url) { data, response, error in
        // ...
        guard let data = data else { return }
        do {
            let articles = try deserializeArticles(from: data)
            databaseQueue.sync {
                updateDatabase(with: articles, for: feed)
            }
        } catch { /* ... */ }
    }
    dataTask.resume()
}

위의 과정을 코드로 나타낸 것 입니다.

이렇게 straight-line 코드를 작성했지만 이 코드에는 몇 가지 숨겨진 성능 이슈가 있습니다.