Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
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
main()
}
}
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
}
@rgkobashi

This comment has been minimized.

Copy link

rgkobashi commented Jul 28, 2017

very useful, thanks!

@iORichy

This comment has been minimized.

Copy link

iORichy commented Aug 31, 2017

works like a charm! thanks

@sphericalwave

This comment has been minimized.

Copy link

sphericalwave commented Dec 29, 2017

Awesome Dood!

@nickkohrn

This comment has been minimized.

Copy link

nickkohrn commented Jan 5, 2018

There is an issue in the steps that you provided for using this code. I made the same mistake, which can be found in my question over on Stack Overflow. According to Apple's documentation, you should not call super at any time.

@dotWasim

This comment has been minimized.

Copy link

dotWasim commented Jan 13, 2018

Cool! and thanks @nickkohrn for your valuable comment!

@Sorix

This comment has been minimized.

Copy link
Owner Author

Sorix commented Feb 9, 2018

@nickkohrn, maybe you don't need to call super if you're directly subclassing Operation. But if you subclass AsynchronousOperation you have to call super, because there are some work is done inside my implementation of that methods. For example state = .executing won't be executed and your implementation will become buggy.

@zhihuitang

This comment has been minimized.

Copy link

zhihuitang commented Feb 14, 2018

state is not thread-safe, setting state might cause TSAN issues.

@sisoje

This comment has been minimized.

Copy link

sisoje commented Mar 4, 2018

that if else in main() though... 4 lines of code waisted

@Sorix

This comment has been minimized.

Copy link
Owner Author

Sorix commented Feb 17, 2019

that if else in main() though... 4 lines of code waisted

Why? You need to flag operation as executing before performing any actions, also operation maybe cancelled even before starting.

@iTamilan

This comment has been minimized.

Copy link

iTamilan commented Apr 14, 2019

Awesome thanks Sorix. I made one example project to understand also.

@DanSkeel

This comment has been minimized.

Copy link

DanSkeel commented May 11, 2019

@christianbilodeau

This comment has been minimized.

Copy link

christianbilodeau commented Oct 26, 2019

@Sorix think he meant that you could have written state = self.isCancelled ? .finished : .executing :)

@Sorix

This comment has been minimized.

Copy link
Owner Author

Sorix commented Nov 15, 2019

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
        someArray.append(1)
    }
    queue.addOperation(operation)
}

queue.waitUntilAllOperationsAreFinished()
print(someArray)

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.