paint-brush
OperationQueue + Asynchroner Code: Alles, was Sie wissen müssenby@bugorbn
488
488

OperationQueue + Asynchroner Code: Alles, was Sie wissen müssen

Boris Bugor5m2024/03/17
Read on Terminal Reader

In Swift scheint die Verwendung einer Operationswarteschlange für synchronen Code die reine Hölle zu sein, da der Code unter der Haube als vollständig gilt, wenn die Kompilierung des Codes abgeschlossen ist. Wenn der asynchrone Code ausgeführt wird, ist der „Vorgang“ selbst bereits abgeschlossen. Um zu verstehen, wie das Problem gelöst werden kann, müssen Sie verstehen, wie der Lebenszyklus des Vorgangs funktioniert.
featured image - OperationQueue + Asynchroner Code: Alles, was Sie wissen müssen
Boris Bugor HackerNoon profile picture

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 Twitter wenn Sie irgendwelche Fragen haben. Außerdem können Sie immer kauf mir einen Kaffee .


Auch hier veröffentlicht