Chapter 3: Queues & Threads
Threads
- 멀티쓰레드의 이점
- 빠른 실행
- 민감성: 사용자가 볼 수 있는 메인 UI쓰레드에서 모든 작업을 실행하면 앱이 느려지거나 멈추게 됩니다.
- 자원 소비 최적화
- Apple은 스레드 관리에 필요한 API를 제공하지만 직접 관리하려고 하면 실제로 성능을 향상시키는 것이 아니라 성능을 저하시킬 수 있습니다.
- OS는 많은 통계를 추적하여 언제 스레드를 할당 또는 파기해야 하는지 알 수 있습니다.
Dispatch queues
- 스레드로 작업하는 방법은
DispatchQueue
를 생성하는 것입니다.
- 대기열을 만들면 OS는 잠재적으로 하나 이상의 스레드를 만들어 대기열에 할당합니다.
- 기존 스레드가 사용 가능한 경우 재사용 할 수 있습니다. 그렇지 않은 경우 OS는 필요에 따라 생성합니다.
The main queue
- 이전 장의 내용을 복기해보면 직렬 또는 동시의 두 가지 유형의 디스패치 큐가 있었습니다.
- 다음 코드와 같이 작성하면 기본 초기화 프로그램은 동시 대기열을 생성합니다.
let label = "com.raywenderlich.mycoolapp.networking" let queue = DispatchQueue(label: label, attributes: .concurrent)
Quality of service
동시 대기열이 필요하지만 직접 관리하고 싶지 않은 경우 DispatchQueue
에서 전역 클래스 메소드를 사용하여 사전 정의 된 전역 대기열 중 하나를 가져올 수 있습니다.
.userInteractive
- 사용자가 직접 상호 작용하는 작업에 권장됩니다.
- UI 업데이트 계산, 애니메이션 또는 UI를 신속하고 신속하게 유지하는 데 필요한 모든 것.
.userInitiated
- 사용자가 UI에서 작업을 즉시 시작할 때 사용해야 하지만 비동기 적으로 수행 할 수 있습니다.
- 예를 들어, 문서를 열거 나 로컬 데이터베이스에서 읽어야 할 수 있습니다.
- 사용자가 버튼을 클릭 한 경우 원하는 대기열 일 수 있습니다.
- 이 대기열에서 수행 된 작업을 완료하는 데 몇 초 정도 걸립니다.
.utility
- 일반적으로 장기 실행 계산, I / O, 네트워킹 또는 연속 데이터 피드와 같은 진행률 표시기가 포함 된 작업에
.utility
발송 대기열을 사용하려고합니다.
- 시스템은 에너지 효율성과 응답 성과 성능의 균형을 유지하려고합니다.
- 이 대기열에서 작업은 몇 초에서 몇 분 정도 걸릴 수 있습니다.
- 일반적으로 장기 실행 계산, I / O, 네트워킹 또는 연속 데이터 피드와 같은 진행률 표시기가 포함 된 작업에
.background
- 사용자가 직접 알지 못하는 작업의 경우
.background
대기열을 사용해야합니다.
- 사용자 상호 작용이 필요하지 않으며 시간에 민감하지 않습니다.
- 프리 페치, 데이터베이스 유지 보수, 원격 서버 동기화 및 백업 수행이 모두 좋은 예입니다.
- OS는 속도 대신 에너지 효율성에 중점을 둘 것입니다.
- 이 대기열을 몇 분 이상 걸리는 데 상당한 시간이 걸리는 작업에 사용하려고 합니다.
- 사용자가 직접 알지 못하는 작업의 경우
.default
and.unspecified
- 가능한 두 가지 다른 선택 사항이 있지만 명시 적으로 사용해서는 안됩니다.
.default
옵션은.userInitiated
와.utility
사이에 있으며 qos 인수의 기본값입니다.
- 직접 사용하기위한 것이 아닙니다.
- 두 번째 옵션은
.unspecified
이며 스레드를 서비스 품질에서 제외시킬 수있는 레거시 API를 지원하기 위해 존재합니다.
- 그것들이 존재한다는 것을 아는 것이 좋지만, 당신이 그것들을 사용하고 있다면 거의 확실하게 무언가 잘못하고 있는 것입니다.
inferring QoS
- 자체 동시 디스패치 큐를 작성하는 경우 초기화 프로그램을 통해 QoS가 무엇인지 시스템에 알려줄 수 있습니다.
let queue = DispatchQueue(label: label, qos: .userInitiated, attributes: .concurrent)
- 현재 컨텍스트가 기본 스레드 인 경우 유추 된 QoS는
.userInitiated
입니다. 직접 QoS를 지정할 수 있지만, QoS가 더 높은 작업을 추가하자마자 대기열의 QoS 서비스가 일치하도록 늘어납니다.
Adding task to queues
- 디스패치 큐는 작업을 큐에 추가하기 위해 동기 및 비동기 메서드를 모두 제공합니다.
- 앱이 시작될 때, 작업별로 "단순히 모든 코드 블록을 실행해야합니다" 라는 의미입니다.
- 또는 예를 들어, 앱을 업데이트하기 위해 서버에 연락해야 할 수도 있습니다.
DispatchQueue.global(qos: .utility).async { [weak self] in
guard let self = self else { return }
// Perform your work here // ...
// Switch back to the main queue to
// update your UI
DispatchQueue.main.async {
self.textLabel.text = "New articles available!"
}
}
- 위의 코드 샘플에서 가져가야 할 두 가지 핵심 사항이 있습니다.
- 첫째, DispatchQueue에는 클로저 규칙을 무효화하는 특별한 것이 없습니다.
- 클로저의 캡처 된 변수 (예 : self)를 활용하려는 경우 여전히 올바르게 처리하고 있는지 확인해야합니다.
- GCD 비동기 클로저에서 자기를 강력하게 캡처하면 전체 클로저가 완료되면 할당이 해제되기 때문에 참조주기 (예 : retain cycle)가 발생하지 않지만 자체 수명이 연장됩니다.
- 예를 들어, 그 동안 해제 된 뷰 컨트롤러에서 네트워크 요청을해도 클로저는 계속 호출됩니다.
- 뷰 컨트롤러를 약하게 캡처하면
nil
이됩니다.
- 그러나 강력하게 캡처하면 클로저가 작업을 완료 할 때까지 뷰 컨트롤러가 활성 상태로 유지됩니다.
- 이를 염두에두고 필요에 따라 약하거나 강하게 캡처하십시오.
- 둘째, UI에 대한 업데이트가 백그라운드 대기열에서 디스패치 내의 메인 대기열로 어떻게 디스패치되는지 확인하십시오.
- 비동기식 호출을 그 내부의 다른 비동기식 호출에게 중첩시키는 것은 괜찮을 뿐 아니라 매우 일반적입니다.
- 작업을 디스패치 큐에 동기적으로 제출할 때는 각별히 주의하십시오.
- 비동기 메서드 대신 동기 메서드를 호출하는 경우 실제로 수행해야 하는 작업인지 한두 번 생각하십시오.
- 현재 대기열을 차단하는 작업을 현재 대기열에 동기식으로 제출하고 작업이 현재 대기열의 리소스에 액세스하려고 하면 앱이 교착 상태가 발생합니다.
- 마찬가지로 메인 대기열에서 동기를 호출하면 UI를 업데이트하는 스레드가 차단되고 앱이 정지 된 것처럼 보입니다.
- 첫째, DispatchQueue에는 클로저 규칙을 무효화하는 특별한 것이 없습니다.
Image loading example
Using a global queue
private func downloadWithGlobalQueue(at indexPath: IndexPath) {
DispatchQueue.global(qos: .utility).async { [weak self] in
guard let self = self else { return }
let url = self.urls[indexPath.item]
guard let data = try? Data(contentsOf: url),
let image = UIImage(data: data) else { return }
DispatchQueue.main.async {
if let cell = self.collectionView
.cellForItem(at: indexPath) as? PhotoCell {
cell.display(image: image)
}
}
}
}
// inside method ..cellForRowAt..
cell.display(image: nil)
downloadWithGlobalQueue(at: indexPath)
Using built-in methods
private func downloadWithUrlSession(at indexPath: IndexPath) {
URLSession.shared.dataTask(with: urls[indexPath.item]) {
[weak self] data, response, error in
guard let self = self,
let data = data,
let image = UIImage(data: data)
else { return }
DispatchQueue.main.async {
if let cell = self.collectionView
.cellForItem(at: indexPath) as? PhotoCell {
cell.display(image: image)
}
}
}.resume()
}
DispatchWorkItem
- 익명 클로저를 전달하는 것 외에도 작업을
DispatchQueue
에 제출하는 다른 방법이 있습니다.
DispatchWorkItem
은 큐에 제출하려는 코드를 보유 할 실제 객체를 제공하는 클래스입니다.
DispatchQueue(label: "xyz").async {
print("The block of code ran!")
}
let workItem = DispatchWorkItem {
print("The block of code ran!")
}
DispatchQueue(label: "xyz").async(execute: workItem)
Canceling a work item
- 명시적
DispatchWorkItem
을 사용하려는 이유 중 하나는 실행 전이나 실행 중에 작업을 취소해야 하기 때문입니다.
- work item에서
cancel()
을 호출하면 다음 두 조치 중 하나가 수행됩니다.- 작업이 대기열에서 아직 시작되지 않은 경우 제거됩니다.
- 작업이 현재 실행중인 경우
isCancelled
속성이true
로 설정됩니다.
- 코드에서
isCancelled
속성을 주기적으로 확인하고 가능한 경우 작업을 취소하기 위해 적절한 조치를 취해야합니다.
Poor man's dependecies
DispatchWorkItem
클래스는 또한 현재 작업 항목이 완료된 후 실행되어야 하는 다른DispatchWorkItem
을 식별하는 데 사용할 수있는notify(queue:execute:)
메소드를 제공합니다.
let backgroundWorkItem = DispatchWorkItem { }
let updateUIWorkItem = DispatchWorkItem { }
backgroundWorkItem.notify(queue: DispatchQueue.main,
execute: updateWorkItem)
DispatchQueue(label: "xyz").async(execute: backgroundWorkItem)
댓글