paint-brush
OperationQueue + Código asincrónico: todo lo que necesita saberpor@bugorbn
532 lecturas
532 lecturas

OperationQueue + Código asincrónico: todo lo que necesita saber

por Boris Bugor5m2024/03/17
Read on Terminal Reader

Demasiado Largo; Para Leer

En Swift, usar una cola de operaciones para código síncrono puede parecer un infierno porque, bajo el capó, el código se considera completo si se completa la compilación de su código. Para cuando se ejecute el código asincrónico, la `Operación` ya se habrá completado. Para comprender cómo resolver el problema, es necesario comprender cómo funciona el ciclo de vida de la operación.
featured image - OperationQueue + Código asincrónico: todo lo que necesita saber
Boris Bugor HackerNoon profile picture

En Swift, usar OperationQueue para código asincrónico puede parecer un infierno porque, bajo el capó, Operations se consideran completas si se completa la compilación de su código sincrónico.


En otras palabras, al compilar el ejemplo que se describe a continuación se generará un orden de ejecución roto ya que, cuando se ejecute el código asincrónico, la Operation en sí ya se habrá completado.


 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 imprimirá:


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


Sin embargo, existe una manera de eludir estas restricciones. Para comprender cómo resolver el problema, es necesario comprender cómo funciona Operation bajo el capó.


La Operation en sí tiene cuatro indicadores mediante los cuales puede rastrear el ciclo de vida de la operación:


  • isReady : indica si la Operation se puede realizar en este momento.


  • isExecuting indica si una Operation está actualmente en curso.


  • isFinished : indica si la Operation está actualmente completada.


  • isCancelled : indica si la Operation fue cancelada.


En teoría, la Operation entra en el estado isFinished antes de que la Operation en sí se ejecute de forma asincrónica, por lo que necesitamos desarrollar una técnica mediante la cual podamos manipular el ciclo de vida de la Operation .


Esta posibilidad se puede resolver subclasificando la Operation y también redefiniendo los métodos start / cancel , así como todos los indicadores sobre los que se construye el ciclo de vida de la operación.


Aquí está el 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) } } }


La subclase que recibimos de la Operation es básica y nos permite completarla manualmente de forma forzada.


Para trabajar con bloques de finalización, debes crear otra subclase. Sin embargo, esta no será una subclase de Operation , sino 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 subclase nos permitirá pasar un cierre a la Operation , después de lo cual se completará la Operation .


Probemos este tipo de operación en la práctica:


 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, pudimos lograr la ejecución sincrónica de Operations :


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


No dudes en contactarme en Gorjeo Si tienes alguna pregunta. Además, siempre puedes cómprame un café .


También publicado aquí