Dans Swift, utiliser OperationQueue
pour du code asynchrone peut sembler un pur enfer car, sous le capot, Operations
sont considérées comme terminées si la compilation de leur code synchrone est terminée.
En d'autres termes, la compilation de l'exemple décrit ci-dessous produira un ordre d'exécution rompu puisque, au moment où le code asynchrone sera exécuté, l' Operation
elle-même sera déjà terminée.
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") }
Ce code imprimera :
First sync operation complete Second sync operation complete First async operation complete Second async operation complete
Il existe cependant un moyen de contourner ces restrictions. Pour comprendre comment résoudre le problème, vous devez comprendre comment fonctionne Operation
sous le capot.
L' Operation
elle-même comporte quatre indicateurs grâce auxquels vous pouvez suivre le cycle de vie de l'opération :
isReady
— indique si l' Operation
peut être effectuée à ce moment-là.
isExecuting
indique si une Operation
est actuellement en cours.
isFinished
: indique si l' Operation
est actuellement terminée.
isCancelled
: indique si l' Operation
a été annulée.
En théorie, l' Operation
entre dans l'état isFinished
avant que l' Operation
elle-même ne soit exécutée de manière asynchrone, nous devons donc développer une technique par laquelle nous pourrons manipuler le cycle de vie de l' Operation
.
Cette possibilité peut être résolue en sous-classant l' Operation
et également en redéfinissant les méthodes start
/ cancel
, ainsi que tous les drapeaux sur lesquels est construit le cycle de vie de l'opération.
Voici le code :
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 sous-classe que nous avons reçue de l' Operation
est basique et nous permet de la compléter manuellement avec force.
Pour travailler avec des blocs de complétion, vous devez créer une autre sous-classe. Cependant, ce ne sera pas une sous-classe de Operation
, mais 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?>? }
Cette sous-classe nous permettra de passer une fermeture à l' Operation
, après quoi l' Operation
sera terminée.
Essayons ce type d'opération en pratique :
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") } )
En conséquence, nous avons pu réaliser une exécution synchrone des Operations
:
First sync operation complete First async operation complete Second sync operation complete Second async operation complete
N'hésitez pas à me contacter au
Également publié ici