ConcurrentProgramming With GCD in Swift3
* 이글은 swift 3.0 버전을 기준으로 작성하였습니다.
애플은 WWDC 2016 에서 Swift 3 에서 GCD 사용한 프로그래밍에 대해 설명했다.
링크 : https://developer.apple.com/videos/play/wwdc2016/720/
swift 2 에서는 비교적 objC 와 비슷한 syntax였는데 swift 3 에서는 조금 생소해서, swift 2 와 비교해서 글을 작성했다.
Swift 2.3 ( ObjC 도 비슷 )
let queue = dispatch_queue_create("label", DISPATCH_QUEUE_SERIAL)
let concurrentQueue = dispatch_queue_create("label", DISPATCH_QUEUE_CONCURRENT)
DISPATCH_QUEUE_SERIAL
DISPATCH_QUEUE_CONCURRENT
Swift 3
let queue = DispatchQueue(label: "label")
let concurrentQueue = DispatchQueue(label: "queuename", attributes: .concurrent)
Swift 3 에서는 기본적으로 serial queue 이고 attributes 를 넣어서 concurrent 를 만든다.
Serial queue:
Serial queue 는 순차적으로 FIFO ( first in / first out )로 동작한다.
Concurrent queue:
Concurrent queue 는 동시적 queue 로 각 작업의 시작과 진행은 GCD 가 컨트롤 한다.
serial queue 를 통해 비동기 작업을 완료한뒤에 main thread 에서 필요한 작업을 수행하는 예제
let queue = DispatchQueue(label: "label", qos: .userInteractive)
queue.async {
//async task
DispatchQueue.main.async {
print("main thread")
}
}
queue.async 를 통해 만들어놓은 queue 에서 비동기 작업을 수행하고
DispatchQueue.main.async 를 통해 메인 스레드에서 작업을 수행한다.
* WWDC 2016 에서 몇개의 큐를 운용할지가 중요 포인트라고 설명했으며 2015 년 WWDC 의 참고 자료를 소개했다.
관련 링크: Building Responsive and Efficient Apps with GCD
Swift 3 로만 설명.
체인 형태는 1번 예제처럼. queue 가 async task 를 끝내고 다른 async queue 를 수행하는 형태를 생각하자. 그룹핑 형태는 queue 들이 각자의 동작을 수행하고 종합해서 수행되는 형태이며 아래에 두 패턴을 잘 표현한 그림이 있다.
Grouping
Swift 3 에서 grouping 은 매우 간단하다.
let group = DispatchGroup()
let queue1 = DispatchQueue(label: "task1")
let queue2 = DispatchQueue(label: "task2")
let queue3 = DispatchQueue(label: "task3")
queue1.async(group: group) {
//async task 1
print("task 1-1")
}
queue1.async(group: group) {
//async task 1
print("task 1-2")
}
queue1.async(group: group) {
//async task 1
print("task 1-3")
}
queue2.async(group: group) {
//async task 2
print("task 2")
}
queue3.async(group: group) {
//async task 3
print("task 3")
}
group.notify(queue: DispatchQueue.main){
print("group notify")
}
let group = DispatchGroup() 로 dispatch 그룹을 만들고,
queue1.async(group: group) 로 그룹에 포함시킨다.
이렇게 같은 콜스택 내에서 serial queue 에 넣어지고 그룹핑 되면, 해당 queue 가 모두 수행된뒤 group notify 가 호출된다.
결과
task 1-1
task 1-2
task 2
task 3
task 1-3
(---- main thread )
group notifiy
* 개인적인 호기심으로, 같은 queue 라고 해도, 다른 콜스택에서 queue에 넣어진다면 어떨까 테스트 해보았는데 해당 task 는 grouping 되지 않았다. ( 당연하게도... )
위의 예제에서 task 1-1 , 1-2 를 main queue 에서 3초 뒤에 queue에 넣도록 수정해보았다.
DispatchLevel.Main.dispatchQueue.asyncAfter(deadline: .now() + 3) {
queue1.async(group: group) {
//async task 1
print("task 1-1")
}
queue1.async(group: group) {
//async task 1
print("task 1-2")
}
}
결과
task 1-3
task 2
task 3
group notifiy
(--- 3초뒤 )
task 1-1
task 1-2
*고백: grouping 은 실제로는 사용해보지 않은 패턴이라서 설명해둔 내용이 부족할 수 있다. OTL
Choosing a Quality of Service
WWDC 2016에서는 queue.async(qos: .background) 와 같이 생성해둔 queue 를 qos 를 골라 수행하는 예제를 보였는데, swift 2에서 보통 사용하던 global queue 를 예제로 바꿔 설명하려한다.
Swift 2.3 ( ObjC 도 비슷 )
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0)) {
}
QOS_CLASS_USER_INTERACTIVE
QOS_CLASS_USER_INITIATED
QOS_CLASS_DEFAULT
QOS_CLASS_UTILITY
QOS_CLASS_BACKGROUND
Swift 3
DispatchQueue.global(qos: .userInitiated).async{
}
DispatchQoS.userInteractive
유저 사용성을 위해 즉시 수행되는 타입.
UI 갱신, 사용자 이벤트 처리,에니메이션 등에 사용한다.
DispatchQoS.userInitiated
비동기 UI queue 에서 수행되지만, 시스템의 다른 작업들보다 우선순위가 높게 수행된다.
일단 시작되면 끝나기 전에 다른 작업이 중간에 다시 시작될일은 거의 없다. 빠르게 수행될 수 있고, UI 상호 작용을 해야 하는 경우에 사용한다.
DispatchQoS.default
시스템에서 제공하는 Qos class 타입을 따르기 위해 사용하는 것으로 다른 qos 와 구분을 위해 정의된 것은 아니다.
DispatchQoS.utility
지속적인 작업이 필요할때 사용할 타입이다. 시스템에서 비교적 높은 레벨로 수행된다.
에너지 효율적(?)으로 동작한다고 한다.
DispatchQoS.background
시간에 민감하지 않은 작업들을 수행할때 사용된다. 언제 수행될지는 GCD 가 컨트롤 한다.
이전 값과 매칭 해보자면 아래와 같다.
* DISPATCH_QUEUE_PRIORITY_HIGH: .userInitiated
* DISPATCH_QUEUE_PRIORITY_DEFAULT: .default
* DISPATCH_QUEUE_PRIORITY_LOW: .utility
* DISPATCH_QUEUE_PRIORITY_BACKGROUND: .background
Swift 3 에서 추가된 객체이다.
DispatchWorkItem 은 queue 에서 실행될 closure 를 가지고 있는 아이템이다. 실행을 컨트롤 할 수 있다.
let item = DispatchWorkItem(flags: .assignCurrentContext) {
print("hello WWDC 2016")
}
queue.async(excute: item)
1. item.wait() 를 호출해서 item 실행이 필요함을 알릴수있다.
2. 이때, 작업의 우선순위를 높일수 있다.
3. wating 상태의 DispatchWorkItem은 ownership 정보를 전달한다.
4. semaphores 와 groups 은 ownership 정보와 다르며 semaphores signal등 스스로의 concept를 따르기 때문. 이 부분은 잘 해석했는지는 불분명 하다. 세마포어와 그룹의 개념은 그것 자체의 signal 등으로 ownership정보가 필요없다는 것으로 이해했는데, 정확하게 알게될때 다시 수정하겠다.
swift 3 에서도 동기화는 중요하다.
WWDC 2016 에서 개발자는
"c 방식에서 사용되는 pthread_mutex_t 는 사용하기 매우 어렵다."
"Foundation.Lock 은 클래스 기반이기때문에 안전하게 사용이 가능하다" 라고 설명했다.
"Derive an Objective-C base class with struct based locks as ivars" 해석하기가 좀... 원문을 보고 이해하자.
* lock 을 사용한 부분은 자세히 언급되어있지 않아서 생략.
사용법은 매우 간단하다.
예제
class MyObject {
private let internalState: Int
private let internalQueue: DispatchQueue
var state: Int {
get {
return internalQueue.sync { internalState }
}
set (newState) {
internalQueue.sync { internalState = newState }
}
}
}
*주의 ! : iOS 10.0 에서만 가능
예제
let queue1 = DispatchQueue(label: "task1")
let queue2 = DispatchQueue(label: "task2")
let item = DispatchWorkItem(flags: .assignCurrentContext) {
if #available(iOS 10.0, *) {
dispatchPrecondition(condition: .onQueue(queue1))
} else {
// Fallback on earlier versions
}
print("work item")
}
queue1.async(execute: item)
queue2.async(execute: item)
결과:
debug 모드에서는 queue2 에서 수행될때 프로그램이 중단된다.
여기까지 Swift 3 의 GCD 사용 변경점 및 , 추가된 내용, 동기화 내용에 대해서 살펴보았다.
* 이외에도 이 포스트에서는 다루지 않은 WWDC 2016 의 GCD 세션에는 GCD Object lifeCycle에 대한 설명도 되어있으니 해당 내용에 관심이 있다면 살펴보자.