/ProcedureKit

A Swift framework inspired by WWDC 2015 Advanced NSOperations session.

Primary LanguageSwiftMIT LicenseMIT

Build status Coverage Status CocoaPods Compatible CocoaPods Documentation Platform Carthage compatible

ProcedureKit

A Swift framework inspired by WWDC 2015 Advanced NSOperations session. Previously known as Operations, developed by @danthorpe.

Resource Where to find it
Session video developer.apple.com
Reference documentation docs.danthorpe.me/operations
Programming guide operations.readme.io
Example projects danthorpe/Examples

Transition to ProcedureKit

Operations has hit a turning point as part of its transition to Swift 3.0, due to the name change of NSOperation to Operation, which now conflicts with its base abstract class. I am taking this opportunity to rename the entire project and move it to an organization repository.

During this transition period, code, documentation and examples will still refer to Operation and NSOperation until the grand renaming occurs.

See #398 for the high level v4.0 roadmap which lists these forthcoming changes.

Usage

The programming guide goes into a lot more detail about using this framework. But here are some of the key details.

Operation is an NSOperation subclass. It is an abstract class which should be subclassed.

import Operations

class MyFirstOperation: Operation {
    override func execute() {
        guard !cancelled else { return }
        print("Hello World")
        finish()
    }
}

let queue = OperationQueue()
let myOperation = MyFirstOperation()
queue.addOperation(myOperation)

the key points here are:

  1. Subclass Operation
  2. Override execute but do not call super.execute()
  3. Check the cancelled property before starting any work.
  4. If not cancelled, always call finish() after the work is done. This could be done asynchronously.
  5. Add operations to instances of OperationQueue.

Observers

Observers are attached to an Operation. They receive callbacks when operation events occur. In a change from Apple's sample code, Operations defines four observer protocols for the four events: did start, did cancel, did produce operation and did finish. There are block based types which implement these protocols. For example, to observe when an operation starts:

operation.addObserver(StartedObserver { op in 
    print("Lets go!")
})

The framework also provides BackgroundObserver, TimeoutObserver and NetworkObserver.

See the programming guide on Observers for more information.

Conditions

Conditions are attached to an Operation. Before an operation is ready to execute it will asynchronously evaluate all of its conditions. If any condition fails, the operation finishes with an error instead of executing. For example:

operation.addCondition(BlockCondition { 
    // operation will finish with an error if this is false
    return trueOrFalse
}

Conditions can be mutually exclusive. This is akin to a lock being held preventing other operations with the same exclusion being executed.

The framework provides the following conditions: AuthorizedFor, BlockCondition, MutuallyExclusive, NegatedCondition, NoFailedDependenciesCondition, SilentCondition, ReachabilityCondition, RemoteNotificationCondition, UserConfirmationCondition and UserNotificationCondition.

See the programming guide on Conditions for more information.

Capabilities

CapabilityType is a protocol which represents the application’s authorization to access device or user account abilities. For example, location services, cloud kit containers, calendars etc. The protocol provides a unified model to:

  1. Check the current authorization status, using GetAuthorizationStatus,
  2. Explicitly request access, using Authorize
  3. Both of the above as a condition called AuthorizedFor.

For example:

class ReminderOperation: Operation {
    override init() {
        super.init()
        name = "Reminder Operation"
        addCondition(AuthorizedFor(Capability.Calendar(.Reminder)))
    }
   
    override func execute() {
        // do something with EventKit here
        finish()
    }
}

The framework provides the following capabilities: Capability.Calendar, Capability.CloudKit, Capability.Health, Capability.Location, Capability.Passbook and Capability.Photos.

See the programming guide on Capabilities for more information.

Logging

Operation has its own internal logging functionality exposed via a log property:

class LogExample: Operation {
   
    override func execute() {
        log.info("Hello World!")
        finish()
    }
}

See the programming guide for more information on logging and supporting 3rd party log frameworks.

Injecting Results

State (or data if you prefer) can be seamlessly transitioned between operations automatically. An operation which produces a result can conform to ResultOperationType and expose state via its result property. An operation which consumes state, can conform to AutomaticInjectionOperationType and set its requirement via its requirement property. Given conformance to these protocols, operations can be chained together:

let getLocation = UserLocationOperation()
let processLocation = ProcessUserLocation()
processLocation.injectResultFromDependency(getLocation)
queue.addOperations(getLocation, processLocation)

See the programming guide on Injecting Results for more information.

Installation

See the programming guide for detailed installation instructions.

CocoaPods

Operations is available through CocoaPods. To install it, simply add the following line to your Podfile:

pod 'Operations'

Carthage

Add the following line to your Cartfile:

github 'danthorpe/Operations'

It was recently discovered that it is not currently possible to install the API extension compatible framework via Carthage. This boiled down to having two schemes for the same platform, and Carthage doesn’t provide a way to pick. As of now, there are two separate projects. One for standard application version, and one for API extension compatible frameworks only. This doesn’t actually solve the problem, but there is a pull request which should allow all projects in a repo to be built. For now, the only semi-automatic way to integrate these flavors is to use Cocoapods: pod 'Operations/Extension'.

Other Advanced NSOperations

Other developers have created projects based off Apple’a WWDC sample code. Check them out too.

  1. PSOperations