멀티스레딩을 하는 이유는 긴 시간이 소요되는 작업을 메인 스레드 밖에서 하기 위해서이다.
앱의 UI는 항상 반응할 수 있는(responsive) 상태를 유지해야 하기 때문이다.
iOS는 멀티스레딩을 큐queue로 관리하는데 큐는 줄지어 선 긴 대기열을 말한다.
Queue에 functions(usually closures)이 들어있고, 순서대로 꺼내어 관련 스레드에서 실행된다.
Queue는 "serial"(one closure a time)과 "concurrent"(multiple threads servicing it)가 있다.
serial queue는 한 번에 하나의 클로저를 실행, concurrent queue는 여러 개의 스레드로 동시 실행된다.
UI와 관련된 건 모두 메인 스레드 메인 큐에서 실행된다 = serial queue
따라서 UI를 방해하는 작업은 메인 큐 이외에 다른 큐에서 작업해야 한다.
main queue 외에서 작업할 때 주로 shared, global, concurrent queue인 global queue를 사용한다.
// Getting the main queue
let mainQueue = DispatchQueue.main
UI activity는 반드시 메인 큐에서 실행되어야 한다.
// Getting a global, shared, concurrent "background" queue
let backgroundQueue = DispatchQueue.global(qos: DispatchQoS)
DispatchQoS.userInteractive // high priority, only do something short and quick
DispatchQoS.userInitiated // high priority, but might take a little bit of time
DispatchQoS.background // not directly initiated by user, so can run as slow as needed
DispatchQoS.utility // long-running background processes, low priority
멀티스레딩은 큐에 클로저를 담는 과정이다. 크게 2가지 방법이 있다.
You can just plop a closure onto a queue and keep running on the current queue
queue.async { ... }
... or you can block the current queue waiting until the closure finishes on that other queue
queue.sync { ... }
main이나 global 이외의 큐가 필요한 경우도 드물게 있다.
// use this only if you have multiple, serially dependent activities
let serialQueue = DispatchQueue(label: "MySerialQ") // serial queue
// rare that you would do this versus global queues
let concurrentQueue = DispatchQueue(label: ""MyConcurrentQ", attributes: .concurrent)
멀티코어 환경에서 코드의 동시 실행을 위한 기술(프레임워크)이다. DispatchQueue는 GCD(Grand Central Dispatch)의 일부이다.
📌 docs
Dispatch Framework
Execute code concurrently on multicore hardware by submitting work to dispatch queues managed by the system.
There are also another API to all of this.
일반적으로 DispatchQueue
API를 사용하지만, 간결한 nesting of dispatching을 위해 OperationQueue
and Operation
이 사용되기도 한다.
let session = URLSession(configuration: .default)
if let url = URL(string: "http://stanford.edu/...") {
let task = session.dataTask(with: url) { (data: Data?, response, error) in
// UI와 관련된 일을 하고 싶다면...
DispatchQueue.main.async {
// do UI stuff here
}
}
task.resume()
}
segue될 view controlle를 navi controller embed in
segue prepare 코드를 조금 수정해야 한다.
// 추가
var destination = segue.destination
if let navcon = destination as? UINavigationController {
destination = navcon.visibleViewController ?? navcon
}
if let imageVC = destination as? ImageViewController {
imageVC.imageURL = url //imageVC의 property(공개 API)
imageVC.title = (sender as? UIButton)?.currentTitle // 버튼 text로 imageVC의 title 설정
}
extension을 활용할 수도 있다.
if let imageVC = segue.destination.contents as? ImageViewController {
imageVC.imageURL = url //imageVC의 property(공개 API)
imageVC.title = (sender as? UIButton)?.currentTitle // 버튼 text로 imageVC의 title 설정
}
extension UIViewController {
var contents: UIViewController {
if let navcon = self as? UINavigationController {
return navcon.visibleViewController ?? self
} else {
return self
}
}
}
private func fetchImage() {
if let url = imageURL {
// global queue로 이미지를 가져오고
DispatchQueue.global(qos: .userInitiated).async { [weak self] in
let urlContents = try? Data(contentsOf: url)
// UI를 업데이트하는 일은 main queue에서
DispatchQueue.main.async {
if let imageData = urlContents, url == self?.imageURL { // image를 가져오는 동안 url이 업데이트될 수 있으므로 가장 최근의 url의 이미지만 가져오도록 체크 조건을 추가한다.
self?.image = UIImage(data: imageData)
}
}
}
}
}
serial, concurrent:
queue의 종류로, 한 번에 하나의 클로저만 처리(serial)하거나 또는 여러 클로저를 동시에 실행(concurrent)한다.
async, sync:
현재 queue의 실행을 방해하느냐 아니냐의 차이, async는 현재 queue의 실행을 방해하지 않고 클로저를 실행시킨다. sync는 클로저가 실행되고 완료될 때까지 현재 queue의 작업을 중지한다.