SoatDev IT Consulting
SoatDev IT Consulting
  • About us
  • Expertise
  • Services
  • How it works
  • Contact Us
  • News
  • September 25, 2023
  • Rss Fetcher
Photo by Austris Augusts on Unsplash

In Swift using OperationQueue for asynchronous code may seem like pure hell because, under the hood, Operations are considered complete if the compilation of their synchronous code is completed.

In other words, compiling the example described below will output a broken execution order since, by the time the asynchronous code is executed, the Operation itself will have already been completed.

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")
}

This will be printed:

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

However, there is a way to circumvent these restrictions. To understand how to solve the problem, you need to understand how Operation works under the hood.

The Operation itself has four flags by which you can track the life cycle of the operation:

  • isReady — indicates whether the Operation can be performed at this time.
  • isExecuting —indicates whether an Operation is currently in progress.
  • isFinished —indicates whether the Operation is currently completed.
  • isCancelled —indicates whether the Operation was cancelled.

In theory, the Operation enters the isFinished state before the Operation itself is executed asynchronously, so we need to develop a technique by which we will be able to manipulate the life cycle of the Operation.

This possibility can be solved by subclassing the Operation and also by redefining the start / cancel methods, as well as all the flags on which the operation’s life cycle is built. Here’s the 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)
}
}
}

The subclass we received from the Operation is basic and allows us to forcefully complete it manually.

To work with completion blocks, you should create another subclass. However, this will not be a subclass of the Operation, but of 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?>?
}

This subclass will allow us to pass a closure to the Operation, after which the Operation will be completed.

Let’s try this type of operation in practice:

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")
}
)

As a result, we were able to achieve synchronous execution of Operations:

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

Don’t hesitate to contact me on Twitter if you have any questions.


OperationQueue + Asynchronous Code was originally published in Better Programming on Medium, where people are continuing the conversation by highlighting and responding to this story.

Previous Post
Next Post

Recent Posts

  • Google launches Doppl, a new app that lets you visualize how an outfit might look on you
  • Why a16z VC believes that Cluely, the ‘cheat on everything’ startup, is the new blueprint for AI startups
  • TechCrunch All Stage: Learn how AI can supercharge your MVPs with Chris Gardner
  • Apple updates the rules for its EU App Store by adding more complicated fees
  • Travis Kalanick is trying to buy Pony AI — and Uber might help

Categories

  • Industry News
  • Programming
  • RSS Fetched Articles
  • Uncategorized

Archives

  • June 2025
  • May 2025
  • April 2025
  • February 2025
  • January 2025
  • December 2024
  • November 2024
  • October 2024
  • September 2024
  • August 2024
  • July 2024
  • June 2024
  • May 2024
  • April 2024
  • March 2024
  • February 2024
  • January 2024
  • December 2023
  • November 2023
  • October 2023
  • September 2023
  • August 2023
  • July 2023
  • June 2023
  • May 2023
  • April 2023

Tap into the power of Microservices, MVC Architecture, Cloud, Containers, UML, and Scrum methodologies to bolster your project planning, execution, and application development processes.

Solutions

  • IT Consultation
  • Agile Transformation
  • Software Development
  • DevOps & CI/CD

Regions Covered

  • Montreal
  • New York
  • Paris
  • Mauritius
  • Abidjan
  • Dakar

Subscribe to Newsletter

Join our monthly newsletter subscribers to get the latest news and insights.

© Copyright 2023. All Rights Reserved by Soatdev IT Consulting Inc.