Operations 3.4.1

Operations 3.4.1

TestsTested
LangLanguage SwiftSwift
License MIT
ReleasedLast Release Dec 2016
SwiftSwift Version 2.3
SPMSupports SPM

Maintained by Dan Thorpe.



Build status

Operations

A Swift framework inspired by WWDC 2015 Advanced NSOperations session.

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

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.

Motivation

I want to stress that this code is heavily influenced by Apple. In no way am I attempting to assume any sort of credit for this architecture - that goes to Dave DeLong and his team. My motivations are that I want to adopt this code in my own projects, and so require a solid well tested framework which I can integrate with.

Other Advanced NSOperations

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

  1. PSOperations