In Swift scheint die Verwendung OperationQueue
für asynchronen Code die reine Hölle zu sein, da Operations
unter der Haube als abgeschlossen gelten, wenn die Kompilierung ihres synchronen Codes abgeschlossen ist.
Mit anderen Worten: Beim Kompilieren des unten beschriebenen Beispiels wird eine fehlerhafte Ausführungsreihenfolge ausgegeben, da zum Zeitpunkt der Ausführung des asynchronen Codes die Operation
selbst bereits abgeschlossen sein wird.
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") }
Dieser Code wird gedruckt:
First sync operation complete Second sync operation complete First async operation complete Second async operation complete
Es gibt jedoch eine Möglichkeit, diese Einschränkungen zu umgehen. Um zu verstehen, wie das Problem gelöst werden kann, müssen Sie verstehen, wie Operation
unter der Haube funktioniert.
Der Operation
selbst verfügt über vier Flags, anhand derer Sie den Lebenszyklus des Vorgangs verfolgen können:
isReady
– gibt an, ob der Operation
zu diesem Zeitpunkt ausgeführt werden kann.
isExecuting
– zeigt an, ob gerade ein Operation
ausgeführt wird.
isFinished
– gibt an, ob der Operation
derzeit abgeschlossen ist.
isCancelled
– gibt an, ob der Operation
abgebrochen wurde.
Theoretisch geht die Operation
in den Status isFinished
über, bevor die Operation
selbst asynchron ausgeführt wird. Daher müssen wir eine Technik entwickeln, mit der wir den Lebenszyklus der Operation
manipulieren können.
Diese Möglichkeit kann gelöst werden, indem die Operation
in Unterklassen unterteilt wird und auch die start
/ cancel
sowie alle Flags, auf denen der Lebenszyklus der Operation aufbaut, neu definiert werden.
Hier ist der 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) } } }
Die Unterklasse, die wir von der Operation
erhalten haben, ist einfach und ermöglicht es uns, sie zwangsweise manuell abzuschließen.
Um mit Vervollständigungsblöcken zu arbeiten, sollten Sie eine weitere Unterklasse erstellen. Dies wird jedoch keine Unterklasse von Operation
sein, sondern von 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?>? }
Diese Unterklasse ermöglicht es uns, einen Abschluss an die Operation
zu übergeben, woraufhin die Operation
abgeschlossen wird.
Probieren wir diese Art von Operation in der Praxis aus:
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") } )
Dadurch konnten wir eine synchrone Ausführung von Operations
erreichen:
First sync operation complete First async operation complete Second sync operation complete Second async operation complete
Zögern Sie nicht, mich unter zu kontaktieren
Auch hier veröffentlicht