[iOS] Operation & Operation Queue

RudinP·2024년 7월 6일
0

Study

목록 보기
250/258
Operation 객체를 만들고 Operation Queue에 추가해서 작업을 추가하는 방법

Operation

하나의 작업을 나타내는 객체이다. 보통은 Operation class를 상속한 Block Operation을 사용하거나 Operation class를 subClassing한 커스텀 Operation을 구현한다.
오퍼레이션은 Single-shot Object인데, 이는 한 번 실행한 작업을 재실행 할 수 없다는 의미이다. 똑같은 작업을 다시 실행하고 싶다면 새로운 인스턴스를 다시 만들어야 한다.
오퍼레이션은 기본적으로 동기 방식으로 실행된다. 비동기 방식을 원한다면 추가적으로 구현해야 한다. 즉, 개별적으로 실행하는 것보다는 오퍼레이션 큐에 추가하는 것이 낫다.
큐에 작업을 추가해두면 오퍼레이션 큐가 작업을 하나씩 꺼내서 실행한다.

장점

  • 오퍼레이션 사이의 의존성을 추가해서 실행 순서를 제어할 수 있다.(Interoperation Dependencies)
  • 실행 취소 구현 가능
  • 작업이 끝난 후 처리를 completion Handler을 통해 구현 가능
  • key-value observing을 통해 실행 상태 감시, 오퍼레이션 사이 우선순위 설정 등 다양한 API 제공

구현

1. Operation Queue 추가

방법은 두가지다.

1번 방법
let queue = OperationQueue.main // 메인스레드에서 작업하는 기본 큐 리턴
    //오퍼레이션에 UI를 업데이트하는 기능이 있다면 사용
2번 방법-직접만들기
let queue = OperationQueue() // 백그라운드에서 실행

2. Operation 추가

  • addOperations는 의존성을 설정하거나 여러개를 동시에 추가 시 사용
  • 하나만 추가하려면 맨 첫번째 메소드를 사용하면 된다.
  • 3번째의 메소드를 사용하면 인스턴스를 추가하지 않고 블록 형태(클로저)로 사용 가능하다. 2번째거를 문법 최적화로 바꾸면 3번째거가 되니 동일한 것이다.

3. Operation 내용 작성

    let queue = OperationQueue() // 백그라운드에서 실행
    
    @IBAction func startOperation(_ sender: Any) {
        //클로저 형태
        queue.addOperation {
            for _ in 1 ... 30 {
                print("😀", separator: " ", terminator: " ")
                Thread.sleep(forTimeInterval: 0.3)
            }
        }
        
        //블록 형태
        //하나의 오퍼레이션에 두 개의 블록 추가할 수 있다는 장점
        let op = BlockOperation {
            for _ in 1 ... 30 {
                print("😡", separator: " ", terminator: " ")
                Thread.sleep(forTimeInterval: 0.6)
            }
        }
        queue.addOperation(op)
        
        //작업이 동시에 실행되는 것은 아니지만, 작업을 종류별로 구분하여 추가 가능-> 가독성 높아짐
        //아직 실행하지 않았거나 실행중인 오퍼레이션에 블록 추가는 문제 없지만, 실행이 끝난 오퍼레이션에 추가하면 크래시가 발생한다.
        op.addExecutionBlock {
            for _ in 1 ... 30 {
                print("🥶", separator: " ", terminator: " ")
                Thread.sleep(forTimeInterval: 0.5)
            }
        }
    }

상태

Ready Executing Finished Cancelled 4 가지 상태가 있다.
오퍼레이션을 만들면 ready,
오퍼레이션 큐에 추가하면 executing,
작업이 정상 완료되면 finished,
작업이 취소되면 cancelled 상태가 된다.

single-shot 특성상 이전 단계로 돌아갈 수는 없다.

취소

아직 시작하지 않은 작업은 언제든지 취소 가능하다.
그러나 실행중인 작업은 취소 기능을 구현해야지만 취소 가능하다.

@IBAction func cancelOperation(_ sender: Any) {
        //큐에 추가된 모든 오퍼레이션이 취소되어야 하지만, 이미 실행중인 오퍼레이션은 취소 기능이 구현되어있지 않다면 불가능하다.
        queue.cancelAllOperations()
    }

이 예제에서 작성한 이모지를 프린트하는 작업에서 매 반복 회차마다 오퍼레이션 상태를 확인하고, 취소되었다면 코드를 종료하도록 구현해야 한다.
하지만 addOperation을 사용하거나 BlockOperation을 사용할 때는 상태를 확인할 수 있는 방법이 없다.

따라서, 취소를 구현하고자 한다면 Operation 클래스를 서브클래싱해서 구현해야만 한다.

Operation Class 서브클래싱

1. Operation을 상속하는 클래스 생성

2. main 함수 오버라이드

class EmojiPrintOperation: Operation{
    let type: String
    
    init(type: String) {
        self.type = type
    }
    
    //중요. 필수.
    override func main() {
        for _ in 1 ..< 100 {
            print(type, separator: " ", terminator: " ")
            Thread.sleep(forTimeInterval: 0.9)
        }
    }
}

3. 취소 기능 구현

isCancelled 속성으로 취소 기능 구현 가능하다.

override func main() {
        for _ in 1 ..< 100 {
            //작업이 취소되면 isCancelled 속성에 true가 저장된다.
            guard !isCancelled else {return} //return으로 인해 종료된다.
            
            print(type, separator: " ", terminator: " ")
            Thread.sleep(forTimeInterval: 0.9)
        }
    }
//viewController.swift
let op2 = EmojiPrintOperation(type: "🤢")
queue.addOperation(op2)
op2.completionBlock = {
            print("Done")
}

  • 취소 기능을 구현한 초록 이모지 오퍼레이션만 Completion Handler을 실행하고 종료된다.

  • 클로저로 구현한 오퍼레이션은 isCancelled에 접근할 수 없기 때문에 해당 방법으로 구현할 수는 없다.

  • 하지만 vc자체에 속성을 추가하고 취소했는지 안했는지 분기를 통해 동일한 패턴으로 구현 가능하다.

profile
iOS 개발자가 되기 위한 스터디룸...

0개의 댓글