관리 메뉴

김종권의 iOS 앱 개발 알아가기

[iOS - swift] NSOperation, Operation, BlockOperation, Custom Operation 사용 방법 (Concurrency) 본문

iOS 응용 (swift)

[iOS - swift] NSOperation, Operation, BlockOperation, Custom Operation 사용 방법 (Concurrency)

jake-kim 2022. 1. 3. 23:39

GCD대신 NSOperation 사용하는 경우 (= GCD에서 구현하기 어려운 기능)

  • 작업 중 cancel 기능이 필요한 경우
  • 작업 시작전에 특별한 다른 작업이 먼저 실행되게끔 하고 싶은 경우
  • 작업들의 실행에 우선순위를 주어서 실행 순서를 정하고 싶은 경우
  • Operation을 서브클래싱하여 작업을 cancel, start하는 것과 같이 작업에 대한 상태를 관리하고 싶은 경우

NSOperation

  • NSOperation은 직접 사용할 수 없는 추상 클래스이므로 NSOperation 서브 클래스로 사용
  • addDependency(op:)메소드를 통해, 어떤 작업 전 다른 것이 먼저 실행됨을 보장할 때 사용 가능
    https://www.appcoda.com/ios-concurrency/
  • queue의 실행 우선 순위를 정할 수 있는 장점
    public enum NSOperationQueuePriority : Int {
        case VeryLow
        case Low
        case Normal
        case High
        case VeryHigh
    }​
  • 지정된 대기열에 대한 특정 작업이나 모든 작업을 cancen() 메소드를 통해 취소 가능
  • 내부의 상태 프로퍼티 finished, ready 를 통해 작업의 진행 상황을 확인 가능
  • NSOperation에는 작업이 완료되면 완료 블록이 호출되도록 설정하는 옵션이 존재

NSOperation의 상태

  • isReady: instantiated 시 해당 상태로 전환
  • isExecuting: start() 메소드 호출 시 해당 상태로 전환
  • isFinished: 작업이 끝난 경우
  • isCncelled: cancel() 메소드 호출 시 해당 상태로 전환

Operation의 사용 1. BlockOperation (Operation의 구현체 중 하나)

  • 하나 이상의 block 객체를 동시에 실행하기 위해 있는 그대로 사용하는 클래스
  • sync + concurrency

  • 사용: BlockOperation() 객체 사용
let operation = BlockOperation()
operation.addExecutionBlock {
  print("a is main thread? (\(Thread.isMainThread))")
  for i in 0...3 {
    print("a(\(i))")
  }
}

operation.addExecutionBlock {
  print("b is main thread? (\(Thread.isMainThread))")
  for i in 0...3 {
    print("b(\(i))")
  }
}

operation.start()
/*
 b is main thread? (false)
 a is main thread? (true)
 a(0)
 b(0)
 a(1)
 a(2)
 a(3)
 b(1)
 b(2)
 b(3)
 */

Operation의 사용 2. Custom Operation

  • Operation을 서브클래싱하여 main() 부분을 구현
  • 위에서 확인한 것과 같이 Main Thread에서 operation을 실행시키므로, main thread에서 동작
class MyOperation: Operation {
  override func main() {
    print("is main thread? (\(Thread.isMainThread))")
    for i in 0...900000 {
      print(i)
    }
  }
}

override func viewDidAppear(_ animated: Bool) {
  super.viewDidAppear(animated)
  let operation = MyOperation()
  operation.start()
  /*
   is main thread? (true)
   0
   1
   2
   3
   4
   ...
   */
}
  • background에서 동작하게끔 하는 방법: start()를 재정의하여 main()부가 background thread에서 불리도록 설계
    • cancel일 경우 작업을 취소
class MyConcurrentOperations: Operation {
  
  override func start() {
    Thread.init(block: main).start() // 첫번째 실행도 background에서 동작하도록 start() 메소드 내부에서 main을 실행하도록 설계
  }
  
  override func main() {
    print("is main thread? (\(Thread.isMainThread))")
    for i in 0...900000 {
      guard isCancelled == false else { return }
      print(i)
    }
  }
}

override func viewDidAppear(_ animated: Bool) {
  super.viewDidAppear(animated)
  
    let operation = MyConcurrentOperations()
    operation.start()
    sleep(1)
    operation.cancel()
    /*
     is main thread? (false)
     0
     1
     2
     3
     4
     ...
     23123
     */
}
  • 이 밖에도 isAsynchronous, isExecuting, 등의 프로퍼티 사용 가능
// 출처: https://medium.com/shakuro/nsoperation-and-nsoperationqueue-to-improve-concurrency-in-ios-e31ee79c98ef

class AsynchronousOperation: Operation {
  enum State: String {
    case Ready
    case Executing
    case Finished
    private var keyPath: String {
      get {
        return "is" + self.rawValue
      }
    }
  }
  var state: State = .Ready {
    willSet {
      willChangeValue(forKey: newValue.rawValue)
      willChangeValue(forKey: newValue.rawValue)
    }
    didSet {
      didChangeValue(forKey: oldValue.rawValue)
      didChangeValue(forKey: oldValue.rawValue)
    }
  }
  override var isAsynchronous: Bool {
    get {
      return true
    }
  }
  override var isExecuting: Bool {
    get {
      return state == .Executing
    }
  }
  override var isFinished: Bool {
    get {
      return state == .Finished
    }
  }
  override func start() {
    if self.isCancelled {
      state = .Finished
    } else {
      state = .Ready
      main()
    }
  }
  override func main() {
    if self.isCancelled {
      state = .Finished
    } else {
      state = .Executing
      //Asynchronous logic (eg: n/w calls) with callback {
    }
  }
}

* 참고

https://medium.com/shakuro/nsoperation-and-nsoperationqueue-to-improve-concurrency-in-ios-e31ee79c98ef

https://ali-akhtar.medium.com/concurrency-in-swift-operations-and-operation-queue-part-3-a108fbe27d61

https://www.appcoda.com/ios-concurrency/

Comments