Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Concurrent NSOperation in Swift
import Foundation
/// An abstract class that makes building simple asynchronous operations easy.
/// Subclasses must implement `execute()` to perform any work and call
/// `finish()` when they are done. All `NSOperation` work will be handled
/// automatically.
open class AsynchronousOperation: Operation {
// MARK: - Properties
private let stateQueue = DispatchQueue(
label: "com.calebd.operation.state",
attributes: .concurrent)
private var rawState = OperationState.ready
@objc private dynamic var state: OperationState {
get {
return stateQueue.sync(execute: { rawState })
}
set {
willChangeValue(forKey: "state")
stateQueue.sync(
flags: .barrier,
execute: { rawState = newValue })
didChangeValue(forKey: "state")
}
}
public final override var isReady: Bool {
return state == .ready && super.isReady
}
public final override var isExecuting: Bool {
return state == .executing
}
public final override var isFinished: Bool {
return state == .finished
}
public final override var isAsynchronous: Bool {
return true
}
// MARK: - NSObject
@objc private dynamic class func keyPathsForValuesAffectingIsReady() -> Set<String> {
return ["state"]
}
@objc private dynamic class func keyPathsForValuesAffectingIsExecuting() -> Set<String> {
return ["state"]
}
@objc private dynamic class func keyPathsForValuesAffectingIsFinished() -> Set<String> {
return ["state"]
}
// MARK: - Foundation.Operation
public override final func start() {
super.start()
if isCancelled {
finish()
return
}
state = .executing
execute()
}
// MARK: - Public
/// Subclasses must implement this to perform their work and they must not
/// call `super`. The default implementation of this function throws an
/// exception.
open func execute() {
fatalError("Subclasses must implement `execute`.")
}
/// Call this function after any work is done or after a call to `cancel()`
/// to move the operation into a completed state.
public final func finish() {
state = .finished
}
}
@objc private enum OperationState: Int {
case ready
case executing
case finished
}
@bygri

This comment has been minimized.

Copy link

@bygri bygri commented Sep 12, 2014

This is really great, except that because the Ready state is set on init, dependencies are ignored. In the superclass's implementation, ready does not return true unless all dependencies are completed.

I changed the getter for ready to return super.ready && state == .Ready in case I want to delay the ready state in init, while also respecting dependencies.

(Also - the first line of init could just be state = .Ready like you have used everywhere else)

@irace

This comment has been minimized.

Copy link

@irace irace commented Mar 3, 2015

@calebd So do you set state = .Executing at the start of all of your overridden start methods, as well as check isCancelled, etc.? I recently implemented one of these in Objective-C, following Apple’s guidelines, and ended up with something that looked like:

- (void)start {
    if (self.isCancelled) {
        self.finished = YES;
    }
    else {
        [self main];

        self.executing = YES;
    }
}
@calebd

This comment has been minimized.

Copy link
Owner Author

@calebd calebd commented Mar 3, 2015

@irace Yep! I typically end up with something just like that.

@jamesbebbington

This comment has been minimized.

Copy link

@jamesbebbington jamesbebbington commented Jun 18, 2015

Hey @calebd have you taken your blog offline? The URL referenced in this gist is no longer resolving.

Could you post what it said here maybe?

Thanks.

@jeremyconkin

This comment has been minimized.

Copy link

@jeremyconkin jeremyconkin commented Jan 26, 2016

Excellent work. Very helpful. Thanks for sharing!

@barbaramartina

This comment has been minimized.

Copy link

@barbaramartina barbaramartina commented Mar 3, 2016

Thanks for sharing! I'm wondering for which use cases are you using asynchronous operations, like the one here, in your applications?.
It seems there is no point at all in using this kind of async implementation of operations if we are running our operations using NSOperationQueues. So I'm trying to think an example in which it would be suitable to use a standalone async op.

I'm basing my comment on this section of Apple Docs:

"When you add an operation to an operation queue, the queue ignores the value of the asynchronous property and always calls the start method from a separate thread. Therefore, if you always run operations by adding them to an operation queue, there is no reason to make them asynchronous."

Asynchronous Versus Synchronous Operations section of:
https://developer.apple.com/library/mac/documentation/Cocoa/Reference/NSOperation_class/#//apple_ref/c/tdef/NSOperationQueuePriority

@aaronrogers

This comment has been minimized.

Copy link

@aaronrogers aaronrogers commented Mar 4, 2016

@barbaramartina sometimes you work with libraries that themselves are asynchronous. You either have to deal with delegates or blocks and can't run all the code from within main.

@KingOfBrian

This comment has been minimized.

Copy link

@KingOfBrian KingOfBrian commented Jun 20, 2016

For those interested in the blog post, it moved here: http://calebmdavenport.tumblr.com/post/123520933906/swift-concurrent-operations

@freaknbigpanda

This comment has been minimized.

Copy link

@freaknbigpanda freaknbigpanda commented Aug 17, 2016

Don't the getters and setters for state need to be thread safe? I'm thinking there needs to be some sort of locking mechanism

@trthtai

This comment has been minimized.

Copy link

@trthtai trthtai commented Apr 21, 2017

This works well. Help me save a lot of time.
Thank you.

@nickkohrn

This comment has been minimized.

Copy link

@nickkohrn nickkohrn commented Apr 22, 2017

I am learning to use the Operation class. Can I ask what the following is used for?

// MARK: - NSObject
@objc private dynamic class func keyPathsForValuesAffectingIsReady() -> Set<String> {
    return ["state"]
}

@objc private dynamic class func keyPathsForValuesAffectingIsExecuting() -> Set<String> {
    return ["state"]
}

@objc private dynamic class func keyPathsForValuesAffectingIsFinished() -> Set<String> {
    return ["state"]
}
@calebd

This comment has been minimized.

Copy link
Owner Author

@calebd calebd commented May 6, 2017

@nickkohrn That tells KVO that any change to state is implicitly a change to isReady, isExecuting, and isFinished.

@diwu

This comment has been minimized.

Copy link

@diwu diwu commented Aug 24, 2017

Thanks for the nice abstraction! I see that you are using a concurrent dispatch queue and the barrier API for the read and write access of the state variable. Is it equivalent of using a serial dispatch queue? Will there be some difference? Once again, great work, thank you!

@robertmryan

This comment has been minimized.

Copy link

@robertmryan robertmryan commented Jan 4, 2018

Do not call super.start() from your override of start. As the documentation for start says (emphasis added):

If you are implementing a concurrent operation, you must override this method and use it to initiate your operation. Your custom implementation must not call super at any time.

@robertmryan

This comment has been minimized.

Copy link

@robertmryan robertmryan commented Jan 4, 2018

@diwu This pattern is known as "reader-writer" synchronization, allowing for concurrent reads, but all writes are synchronized. This is like the serial dispatch queue pattern, but is conceptually a little more efficient. The difference is only material in high-contention environments, but it doesn't hurt.

BTW, the reader-writer pattern is discussed in the latter part of WWDC 2012 video Asynchronous Design Patterns with Blocks, GCD, and XPC. Note, that video is using the old Swift 2 and Objective-C GCD syntax, but the idea is identical to what you see here.

@robertmryan

This comment has been minimized.

Copy link

@robertmryan robertmryan commented Jan 8, 2018

By the way, I notice that this sample is doing the manual KVN of state. Because it’s a dynamic property, that is not needed. It does the KVN for you.

@jleach

This comment has been minimized.

Copy link

@jleach jleach commented Apr 12, 2018

@calebd What's the licence of the this gist?

@lukas2

This comment has been minimized.

Copy link

@lukas2 lukas2 commented Mar 11, 2019

Thank you for sharing this.

@YuanfuC

This comment has been minimized.

Copy link

@YuanfuC YuanfuC commented May 17, 2021

I am learning to use the Operation class. Can I ask what the following is used for?

// MARK: - NSObject
@objc private dynamic class func keyPathsForValuesAffectingIsReady() -> Set<String> {
    return ["state"]
}

@objc private dynamic class func keyPathsForValuesAffectingIsExecuting() -> Set<String> {
    return ["state"]
}

@objc private dynamic class func keyPathsForValuesAffectingIsFinished() -> Set<String> {
    return ["state"]
}

Here is Registering Dependent Keys

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