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 |
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:
- Subclass
Operation
- Override
execute
but do not callsuper.execute()
- Check the
cancelled
property before starting any work. - If not cancelled, always call
finish()
after the work is done. This could be done asynchronously. - Add operations to instances of
OperationQueue
.
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 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.
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:
- Check the current authorization status, using
GetAuthorizationStatus
, - Explicitly request access, using
Authorize
- 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.
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.
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.
See the programming guide for detailed installation instructions.
Operations is available through CocoaPods. To install it, simply add the following line to your Podfile:
pod 'Operations'
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'
.
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 developers have created projects based off Apple’a WWDC sample code. Check them out too.