오퍼레이션(Operation)은 하나의 작업을 나타내는 객체입니다. 보통 오퍼레이션 클래스를 상속한 블록 오퍼레이션을 사용하거나 오퍼레이션 클래스를 서브클래싱하여 커스텀 오퍼레이션을 직접 구현합니다.
오퍼레이션을 사용할 때 얻는 이점
- 오퍼레이션 사이에 의존성을 추가하여 실행 순서를 제어할 수 있습니다.
- 실행 취소(cancel) 기능을 구현하고 컴플리션 핸들러(completion handler)를 추가하는데 필요한 API를 제공합니다.
- 오퍼레이션 상태를 감시할 때는 KVO를 활용하거나 오퍼레이션 클래스가 제공하는 속성을 사용합니다.
오퍼레이션의 특징
- 오퍼레이션은 Single-Shot Object 입니다. 즉, 실행이 완료된 인스턴스는 다시 실행할 수 없습니다.
- 동일한 작업을 반복해야하는 경우에는 매번 새로운 인스턴스를 생성해야 합니다.
- 우선순위를 설정할 수 있습니다.
오퍼레이션의 4가지 상태
- Ready: 작업을 실행할 수 있는 상태
- Executing: 작업이 시작되어 실행중인 상태
- Finished: 정상적으로 작업이 종료된 상태
- Cancelled: 작업을 취소한 상태
Finished나 Cancelled 상태에서 Ready 상태로 돌아가는 것은 불가능합니다.
오퍼레이션 동작 방식
오퍼레이션은 API를 통해 직접 실행할 수 있습니다. 하지만 다른 코드와 동시에 실행되는 것을 보장하지 않습니다. 이것은 오퍼레이션이 기본적으로 동기 방식으로 실행되기 때문입니다. 비동기 방식으로 실행되도록 구현할 수 있지만 권장되지는 않습니다. 오퍼레이션을 직접 실행하는 것보다 오퍼레이션 큐에 추가하는 것을 추천합니다.
오퍼레이션 큐(OperationQueue)는 이름 그대로 오퍼레이션을 큐로 관리합니다. 오퍼레이션으로 실행할 작업을 구현한 다음 오퍼레이션 큐에 추가하면 나머지 부분은 오퍼레이션 큐가 담당합니다.
오퍼레이션의 실행 순서는 우선 순위나 의존성에 따라서 자동으로 결정됩니다. 오퍼레이션 큐는 사용 가능한 CPU 코어와 시스템 리소스를 활용해서 동시에 실행할 수 있는 작업 수를 결정하고 최대한 빠르게 실행합니다.
정상적으로 실행이 완료된 작업은 큐에서 자동으로 제거됩니다. 아직 시작되지 않은 작업은 언제든지 취소할 수 있습니다. 현재 실행중인 작업은 취소 기능이 구현된 경우에만 취소할 수 있습니다. 나머지 경우에는 취소 여부에 관계없이 계속 실행되게 됩니다. 커스텀 오퍼레이션을 구현할 때는 항상 취소 기능을 고려하여 구현하는 것이 좋습니다.
오퍼레이션의 우선 순위
오퍼레이션 우선 순위는 두 가지 속성으로 설정합니다.
QueuePriority
동일한 큐에 추가되어 있는 오퍼레이션 사이의 상대적인 우선 순위를 설정할 수 있습니다. 높은 우선 순위를 가진 오퍼레이션이 먼저 실행됩니다. veryHigh부터 veryLow까지 (veryHigh, high, normal, low, veryLow) 설정가능하며 직접 설정하지 않으면 normal로 설정됩니다.
QualityOfService
리소스 사용 우선 순위를 결정합니다. 줄여서 QoS 라고도 부릅니다. 우선 순위가 높을수록 CPU, 네트워크, 디스크 등을 더 오랜 시간 사용할 수 있고 더 빠르게 실행합니다. userInteractive, userInitiated, utility, background 4단계로 설정가능하며 기본값은 background 입니다. 보통 기본값을 그대로 사용해도 큰 문제는 없습니다.
오퍼레이션 사용 방법
오퍼레이션 큐를 생성하는 방법은 2가지가 있습니다.
// UI를 업데이트하는 오퍼레이션은 메인에 추가해야함
let mainQueue = OperationQueue.main
// 백그라운드 큐에서 작업을 실행하는 오퍼레이션 큐
let queue = OperationQueue()
오퍼레이션 큐에 꽃을 프린트하는 오퍼레이션을 추가해 보겠습니다. 3가지 방법을 알아보겠습니다.
// 블록 형태로 바로 큐에 추가하는 방법
queue.addOperation {
autoreleasepool {
for _ in 1..<100 {
print("🌸")
}
}
}
오퍼레이션 인스턴스를 직접 생성하지 않고 블록 형태로 바로 큐에 추가하였습니다. 오퍼레이션은 메모리 관리를 직접 처리해주지 않습니다. 그러므로 블록이나 커스텀 오퍼레이션에는 autoreleasepool 을 직접 추가해야 합니다.
// 인스턴스를 생성하여 추가하는 방법
let op = BlockOperation {
autoreleasepool {
for _ in 1..<100 {
print("🌼")
}
}
}
queue.addOperation(op)
블록 오퍼레이션(BlockOperation) 인스턴스를 생성하여 큐에 추가하였습니다. 블록 오퍼레이션의 장점은 하나의 오퍼레이션에 두 개 이상의 블록을 추가할 수 있다는 것입니다.
op.addExecutionBlock {
autoreleasepool {
for _ in 1..<100 {
print("🌺")
}
}
}
이렇게 추가한 블록은 나머지 블록과 동시에 실행됩니다. 아직 실행되지 않았거나 실행중인 블록에 새로운 블록을 추가하는 것은 문제가 전혀 없습니다. 그러나 실행이 완료된 경우에는 예외가 발생하므로 주의해야 합니다.
마지막으로 커스텀 오퍼레이션을 만들어 큐에 추가해보겠습니다.
class CustomOperation: Operation {
let flower: String
init(flower: String) {
self.flower = flower
}
// main에 실제로 실행할 작업을 구현합니다.
override func main() {
autoreleasepool {
for _ in 1..<100 {
// 취소 상태를 체크하여 작업을 취소
guard !isCancelled else { return }
print(flower)
}
}
}
}
let op2 = CustomOperation(flower: "🌹")
queue.addOperation(op2)
커스텀 오퍼레이션에 취소 상태(isCancelled)를 계속 체크하여 취소될 경우 작업이 중지될 수 있도록 구현하였습니다. 블록으로 큐에 추가하는 경우에는 isCancelled 속성에 접근할 수가 없기 때문에 상태를 체크할 수 있는 변수를 직접 정의하여 사용합니다.
var isCancelled = false
queue.addOperation {
autoreleasepool {
for _ in 1..<100 {
guard !self.isCancelled else { return }
print("🌸")
}
}
}
isCancelled = true
queue.cancelAllOperations()
cancelAllOperations 메소드는 단순히 오퍼레이션의 isCancelled 속성을 true로 바꾸는 것 뿐입니다. isCancelled 속성을 확인하고 작업을 취소하도록 구현되어 있는 경우에만 정상적으로 작업이 취소됩니다.
컴플리션 핸들러도 추가할 수 있습니다.
op.completionBlock = {
print("Complete!")
}
컴플리션 블록(completionBlock)은 오퍼레이션에 구현된 작업이 완료된 후에 호출되게 됩니다.
정리
- 오퍼레이션의 특징을 살펴보았습니다.
- 보통 오퍼레이션은 오퍼레이션 큐에 추가하여 활용합니다.
- 오퍼레이션 큐에 오퍼레이션을 추가하여 사용하는 방법을 알아보았습니다.
- 오퍼레이션을 효율적으로 사용할 수 있는 다양한 API가 제공되고 있습니다.
'iOS' 카테고리의 다른 글
[iOS] UIView Animation으로 애니메이션 구현하는 방법 (0) | 2019.12.15 |
---|---|
[iOS] 앱 실행시 일어나는 일들 (App Launch Sequence) (0) | 2019.12.15 |
[iOS] 강한 참조 사이클 (순환 참조) 해결하기 2편 (0) | 2019.12.15 |
[iOS] 강한 참조 사이클 (순환 참조) 해결하기 1편 (0) | 2019.12.15 |
[iOS] ARC 란? (Automatic Reference Counting) (0) | 2019.12.15 |