Skip to content

Instantly share code, notes, and snippets.

Last active May 22, 2024 07:15
Show Gist options
  • Save Sorix/57bc3295dc001434fe08acbb053ed2bc to your computer and use it in GitHub Desktop.
Save Sorix/57bc3295dc001434fe08acbb053ed2bc to your computer and use it in GitHub Desktop.
Subclass of NSOperation (Operation) to make it asynchronous in Swift 3, 4, 5
// Created by Vasily Ulianov on 09.02.17, updated in 2019.
// License: MIT
import Foundation
/// Subclass of `Operation` that adds support of asynchronous operations.
/// 1. Call `super.main()` when override `main` method.
/// 2. When operation is finished or cancelled set `state = .finished` or `finish()`
open class AsynchronousOperation: Operation {
public override var isAsynchronous: Bool {
return true
public override var isExecuting: Bool {
return state == .executing
public override var isFinished: Bool {
return state == .finished
public override func start() {
if self.isCancelled {
state = .finished
} else {
state = .ready
open override func main() {
if self.isCancelled {
state = .finished
} else {
state = .executing
public func finish() {
state = .finished
// MARK: - State management
public enum State: String {
case ready = "Ready"
case executing = "Executing"
case finished = "Finished"
fileprivate var keyPath: String { return "is" + self.rawValue }
/// Thread-safe computed state value
public var state: State {
get {
stateQueue.sync {
return stateStore
set {
let oldValue = state
willChangeValue(forKey: state.keyPath)
willChangeValue(forKey: newValue.keyPath)
stateQueue.sync(flags: .barrier) {
stateStore = newValue
didChangeValue(forKey: state.keyPath)
didChangeValue(forKey: oldValue.keyPath)
private let stateQueue = DispatchQueue(label: "AsynchronousOperation State Queue", attributes: .concurrent)
/// Non thread-safe state storage, use only with locks
private var stateStore: State = .ready
Copy link

Sorix commented Jun 3, 2020

As per the Apple Documentation, you will need only main function to operate the synchronous operation. For asynchronous operation, you are required to override start function and instance properties(isAsynchronous, isExecuting and isFinished). Also, you need to call the functions for asynchronous behaviour from start function. But here its not mentioned anything about the same.

I overrode the methods you mentioned. You could call async code anywhere you want, it's up to you to override methods in your operation.

Copy link

spiridonkopicl commented Sep 17, 2021

I updated my code to make it a thread-safe one. Please update applications that use that code to avoid cryptic fatal errors like EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0).

In spite of that code is a thread-safe, please don't crush your application if you use a non-thread safe code inside your operation's completion or inside an operation, e.g.:

var someArray = [Int]()
let queue = OperationQueue()

for _ in 1...10000 {
    // For test purposes I've added `finish()` inside that async operation to finish that operation
    let operation = AsynchronousOperation()
    operation.completionBlock = {
        // We concurrently modifing a shared array from all operations
        // Setting data is not thread-safe, at some point we will receive a crash


P.S. Thanks to @DanSkeel, @zhihuitang who noted that.

@Sorix I have a question related to this. I update dictionary as you did with array. How that can be solved? So basically result of every operation I need to keep somewhere in order to access it later. thx

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment