paint-brush
OperationQueue + código assíncrono: tudo o que você precisa saberby@bugorbn
488
488

OperationQueue + código assíncrono: tudo o que você precisa saber

Boris Bugor5m2024/03/17
Read on Terminal Reader

Em Swift, usar uma fila de operações para código síncrono pode parecer um verdadeiro inferno porque, nos bastidores, o código é considerado completo se a compilação de seu código for concluída. No momento em que o código assíncrono for executado, a `Operação` já terá sido concluída. Para entender como resolver o problema, é preciso entender como funciona o ciclo de vida da operação.
featured image - OperationQueue + código assíncrono: tudo o que você precisa saber
Boris Bugor HackerNoon profile picture

Em Swift, usar OperationQueue para código assíncrono pode parecer um verdadeiro inferno porque, nos bastidores, Operations são consideradas concluídas se a compilação de seu código síncrono for concluída.


Em outras palavras, compilar o exemplo descrito abaixo gerará uma ordem de execução quebrada, pois, no momento em que o código assíncrono for executado, a própria Operation já terá sido concluída.


 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") }


Este código irá imprimir:


 First sync operation complete Second sync operation complete First async operation complete Second async operation complete


No entanto, existe uma maneira de contornar essas restrições. Para entender como resolver o problema, você precisa entender como Operation funciona nos bastidores.


A Operation em si possui quatro sinalizadores pelos quais você pode acompanhar o ciclo de vida da operação:


  • isReady — indica se a Operation pode ser executada neste momento.


  • isExecuting — indica se uma Operation está em andamento.


  • isFinished — indica se a Operation está concluída no momento.


  • isCancelled — indica se a Operation foi cancelada.


Em teoria, a Operation entra no estado isFinished antes da própria Operation ser executada de forma assíncrona, portanto, precisamos desenvolver uma técnica pela qual seremos capazes de manipular o ciclo de vida da Operation .


Esta possibilidade pode ser resolvida subclassificando a Operation e também redefinindo os métodos start / cancel , bem como todos os flags sobre os quais é construído o ciclo de vida da operação.


Aqui está o código:


 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) } } }


A subclasse que recebemos da Operation é básica e nos permite completá-la manualmente.


Para trabalhar com blocos de conclusão, você deve criar outra subclasse. Porém, esta não será uma subclasse de Operation , mas sim de 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?>? }


Esta subclasse nos permitirá passar um encerramento para a Operation , após o qual a Operation será concluída.


Vamos tentar este tipo de operação na prática:


 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") } )


Como resultado, conseguimos a execução síncrona de Operations :


 First sync operation complete First async operation complete Second sync operation complete Second async operation complete


Não hesite em contactar-me em Twitter se você tiver quaisquer perguntas. Além disso, você sempre pode me compre um café .


Também publicado aqui