В 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
переходит в состояние isFinished
до того, как сама Operation
будет выполнена асинхронно, поэтому нам необходимо разработать технику, с помощью которой мы сможем манипулировать жизненным циклом 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
Не стесняйтесь обращаться ко мне по
Также опубликовано здесь