Swift에서 비동기 코드에 OperationQueue
사용하는 것은 완전히 지옥처럼 보일 수 있습니다. 내부적으로는 동기 코드의 컴파일이 완료되면 Operations
완료된 것으로 간주되기 때문입니다.
즉, 아래 설명된 예제를 컴파일하면 비동기 코드가 실행될 때 Operation
자체가 이미 완료되었기 때문에 잘못된 실행 순서가 출력됩니다.
let operationQueue = OperationQueue() operationQueue.maxConcurrentOperationCount = 1 operationQueue.addOperation { DispatchQueue.main.asyncAfter(deadline: .now() + 1) { print("First async operation complete") } print("First sync operation complete") } operationQueue.addOperation { DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) { print("Second async operation complete") } print("Second sync operation complete") }
이 코드는 다음을 인쇄합니다:
First sync operation complete Second sync operation complete First async operation complete Second async operation complete
그러나 이러한 제한을 우회할 수 있는 방법이 있습니다. 문제를 해결하는 방법을 이해하려면 Operation
내부적으로 어떻게 작동하는지 이해해야 합니다.
Operation
자체에는 작업의 수명 주기를 추적할 수 있는 네 가지 플래그가 있습니다.
isReady
— 현재 Operation
수행할 수 있는지 여부를 나타냅니다.
isExecuting
— Operation
현재 진행 중인지 여부를 나타냅니다.
isFinished
— Operation
현재 완료되었는지 여부를 나타냅니다.
isCancelled
- Operation
취소되었는지 여부를 나타냅니다.
이론적으로 Operation
자체가 비동기적으로 실행되기 전에 Operation
이 isFinished
상태에 진입하므로 Operation
의 수명 주기를 조작할 수 있는 기술을 개발해야 합니다.
이 가능성은 Operation
서브클래싱하고 start
/ cancel
메서드와 작업 수명 주기가 구축된 모든 플래그를 재정의하여 해결할 수 있습니다.
코드는 다음과 같습니다.
public class AsyncOperation: Operation { // MARK: Open override open var isAsynchronous: Bool { true } override open var isReady: Bool { super.isReady && self.state == .ready } override open var isExecuting: Bool { self.state == .executing } override open var isFinished: Bool { self.state == .finished } override open func start() { if isCancelled { state = .finished return } main() state = .executing } override open func cancel() { super.cancel() state = .finished } // MARK: Public public enum State: String { case ready case executing case finished // MARK: Fileprivate fileprivate var keyPath: String { "is" + rawValue.capitalized } } public var state = State.ready { willSet { willChangeValue(forKey: newValue.keyPath) willChangeValue(forKey: state.keyPath) } didSet { didChangeValue(forKey: oldValue.keyPath) didChangeValue(forKey: state.keyPath) } } }
Operation
에서 받은 하위 클래스는 기본 클래스이며 이를 수동으로 강제로 완료할 수 있습니다.
완성 블록을 사용하려면 다른 하위 클래스를 만들어야 합니다. 그러나 이는 Operation
의 하위 클래스가 아니라 AsyncOperation
의 하위 클래스입니다.
public typealias VoidClosure = () -> Void public typealias Closure<T> = (T) -> Void public class CompletionOperation: AsyncOperation { // MARK: Lifecycle public init(completeBlock: Closure<VoidClosure?>?) { self.completeBlock = completeBlock } // MARK: Public override public func main() { DispatchQueue.main.async { [weak self] in self?.completeBlock? { DispatchQueue.main.async { self?.state = .finished } } } } // MARK: Private private let completeBlock: Closure<VoidClosure?>? }
이 서브클래스를 사용하면 Operation
에 클로저를 전달할 수 있으며 그 후에 Operation
완료됩니다.
실제로 이런 유형의 작업을 시도해 보겠습니다.
let operationQueue = OperationQueue() operationQueue.maxConcurrentOperationCount = 1 operationQueue.addOperation( CompletionOperation { completion in DispatchQueue.main.asyncAfter(deadline: .now() + 1) { print("First async operation complete") completion?() } print("First sync operation complete") } ) operationQueue.addOperation( CompletionOperation { completion in DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) { print("Second async operation complete") completion?() } print("Second sync operation complete") } )
그 결과, Operations
의 동기 실행을 달성할 수 있었습니다.
First sync operation complete First async operation complete Second sync operation complete Second async operation complete
주저하지 말고 저에게 연락주세요
여기에도 게시됨