기존의 DispatchQueue에서의 작업은 다음과 같이 클로저 형태로 작성하였다. 

DispatchQueue.global().sync {
    task(1)
    task(2)
}

DispatchQueue.global().async {
    task(1)
    task(2)
}

하지만, 같은 작업이 반복되는 경우 똑같은 코드를 작성해야 하는데, 

DipsatchWorkItem을 사용하면 작업들을 캡슐화하여 재사용성을 늘릴 수 있다.

 

 

DispatchWorkItem 

DispatchWorkItem은 작업들을 캡슐화 할 수 있는데, 사용은 다음과 같다. 

let workItem = DispatchWorkItem {
    task(1)
    task(2)
}

DispatchQueue.global().async(execute: workItem)

 

DispatchWorkItem의 initializer를 자세히 살펴보면 아래와 같이 구성되어 있다.

init(
    qos: DispatchQoS = .unspecified,
    flags: DispatchWorkItemFlags = [],
    block: @escaping () -> Void
)

작업에 대한 Qos를 지정할 수 있으며, 

block에 작업에 대한 클로져를 작성한다. 

또한, DispatchWorkItemFlags는 work Item에 대한 Qos 혹은 추가적인 동작들을 지정할 수 있다. 

 

DispatchWorkItem은 크게 4가지으 메서드를 추가적으로 제공하는데 이들에 대해 알아보자. 

 

 

perform()

perform 메서드는 workItem을 현재 Thread에서 동기적으로 실행한다. Main Thread에서 실행될 경우, work Item의 task가 오래 걸린다면 UI Update가 멈출 수 있기 때문에 주의해야 한다.

workItem.perform()

 

 

wait() 

DispatchGroup에서 다뤘던 wait() 메서드와 유사하다. 

public func wait()
//모든 작업이 끝날때까지 기다림

public func wait(timeout: DispatchTime) -> DispatchTimeoutResult
//timeout만큼 기다리고 해당 시간안에 작업을 모두 수행하면 .success를 아니면 .timedOut을 반환한다.

public func wait(wallTimeout timeout: DispatchWallTime) -> DispatchTimeoutResult
// 위의 메서드와 비슷하지만, 
// DispatchTime은 나노세컨드의 정확도를 가지고 있고, DispatchWallTime은 마이크로 세컨드의 정확도를 가지고 있다고 한다.

 

 

notify()

이 역시 DispatchGroup에서 다뤘던 notify()와 유사하다. 

workItem.notify(queue: .main) {
    print("its finish")
}

notify의 completion handler 부분을 DispatchWorkItem으로 대체가 가능하다. 

let notifyWorkItem = DispatchWorkItem {
    print("work item finish")
}
workItem.notify(queue: .main, execute: notifyWorkItem)

DispatchGroup의 notify역시 completion handler 부분을 DispatchWorkItem으로 대체가 가능하다. 

 

 

cancel()

cancel()메서드를 호출하면,

workItem의 isCancelled 프로퍼티가 true로 설정된다.

isCancelled 프로퍼티가 true인 workItem이 실행되기 전의 상태라면, 해당 작업은 Queue에서 제거된다.

수행된 작업에 대해서는 isCancelled의 프로퍼티만 true로 설정할 뿐, 중단되지는 않는다. 

 

다음과 같이 task와 workItem이 있다고 하자. 

func task(_ taskNumber: Int) {
    print("task\(taskNumber) start")
    sleep(2)
    print("task\(taskNumber) finish")

}

let workItem1 = DispatchWorkItem {
    task(1)
}

let workItem2 = DispatchWorkItem {
    task(2)
}

let workItem3 = DispatchWorkItem {
    task(3)
}

workItem1.notify(queue: .main) {
    print("workItem1 finish, isCancelled: \(workItem1.isCancelled)")
}

workItem2.notify(queue: .main) {
    print("workItem2 finish, isCancelled: \(workItem2.isCancelled)")
    
}

workItem3.notify(queue: .main) {
    print("workItem3 finish, isCancelled: \(workItem3.isCancelled)")
    
}

 

//그냥 수행
DispatchQueue.global().async(execute: workItem1)

//수행도중 cancel()
DispatchQueue.global().async(execute: workItem2)
sleep(3)
workItem2.cancel()

//수행 전에 cancel()
workItem3.cancel()
DispatchQueue.global().async(execute: workItem3)

/* prints:
 task1 start
 task2 start
 task1 finish
 task2 finish
 workItem1 finish, isCancelled: false
 workItem2 finish, isCancelled: true
 workItem3 finish, isCancelled: true
*/

실제 결과를 살펴보면, workItem1은 정상 수행이 되며, isCancelled 프로퍼티가 false인 것을 볼 수 있다. 

workItem2같은 경우는 수행도중 cancel()메서드를 호출했기 때문에, 수행은 정상적으로 작동되지만, isCancelled 프로퍼티만 true인 것을 볼 수 있다. 

workItem3의 경우는 수행 전에 cancel()메서드를 호출했기 때문에, 수행도 되지 않으며, isCancelled프로퍼티도 false인 것을 볼 수 있다.

 

 

Reference

https://younggyun.tistory.com/24

DispatchWorkItem

복사했습니다!