Skip to content

Instantly share code, notes, and snippets.

@Sorix
Last active June 15, 2023 10:50
Show Gist options
  • Star 86 You must be signed in to star a gist
  • Fork 12 You must be signed in to fork a gist
  • 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
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
}
@spiridonkopicl
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
        someArray.append(1)
    }
    queue.addOperation(operation)
}

queue.waitUntilAllOperationsAreFinished()
print(someArray)

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