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
También publicado aquí