paint-brush
OperationQueue + асинхронный код: все, что вам нужно знатьby@bugorbn
488
488

OperationQueue + асинхронный код: все, что вам нужно знать

Boris Bugor5m2024/03/17
Read on Terminal Reader

В Swift использование очереди операций для синхронного кода может показаться настоящим адом, потому что под капотом код считается завершенным, если его компиляция завершена. К моменту выполнения асинхронного кода сама «Операция» уже будет завершена. Чтобы понять, как решить проблему, нужно понять, как устроен жизненный цикл операции.
featured image - OperationQueue + асинхронный код: все, что вам нужно знать
Boris Bugor HackerNoon profile picture

В 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


Не стесняйтесь обращаться ко мне по Твиттер если у вас есть вопросы. Также вы всегда можете купи мне кофе .


Также опубликовано здесь