pluralsight / PSOperations

A framework for advanced NSOperations usage
https://developer.apple.com/videos/wwdc/2015/?id=226
Apache License 2.0
659 stars 103 forks source link

PSOperations

codebeat badge Build Status

PSOperations is a framework that leverages the power of NSOperation and NSOperationQueue. It enables you to use operations more easily in all parts of your project.

This is an adaptation of the sample code provided in the Advanced NSOperations session of WWDC 2015.

Support

Swift 3+

Because Swift 3 removes the NS prefix on several Foundation types we've added a few typealiases for convenience. We investigated renaming the few classes that conflict but ran into radar://28917706 where frameworks will fallback to Foundation types if the framework doesn't contain the given type i.e. UIKit.Data is valid and really is Foundation.Data. If we were to rename Operation to PSOperation usuages of PSOperations.Operation would end up using Foundation.Operation and potentially break your code.

Here are the typealiases:

public typealias PSOperation = Operation
public typealias PSOperationQueue = OperationQueue
public typealias PSOperationQueueDelegate = OperationQueueDelegate
public typealias PSBlockOperation = BlockOperation

Installation

PSOperations supports multiple methods for installing the library in a project.

Swift Package Manager

The Swift Package Manager (SPM) is Swift's own dependency management as of Swift 3.0. Xcode 11 gained native support for SPM and allows to add dependencies to apps from within Xcode.

PSOperations can be added either via Xcode 11+, or by adding the following dependency to your Package.swift:

.package(url: "https://github.com/pluralsight/PSOperations.git", from: "5.0.2"),

CocoaPods

CocoaPods is a dependency manager for Objective-C, which automates and simplifies the process of using 3rd-party libraries like PSOperations in your projects.

You can install it with the following command:

$ gem install cocoapods

To integrate PSOperations into your Xcode project using CocoaPods, specify it in your Podfile.
If you want all the child subspecs (Health and Passbook capabilities):

source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '8.0'

target 'TargetName' do
pod 'PSOperations', '~> 4.0'
end

Then, run the following command:

$ pod install

Alternative configurations:
Core functionality, excluding capabilities:

pod 'PSOperations/Core', '~> 4.0'

Core functionality, including only the Passbook capability:

pod 'PSOperations/Passbook', '~> 4.0'

Core functionality, including only the Health capability:

pod 'PSOperations/Health', '~> 4.0'

Core functionality, including only the Calendar capability:

pod 'PSOperations/Calendar', '~> 4.0'

Core functionality, including only the Location capability and operation:

pod 'PSOperations/Location', '~> 4.0'

Carthage

Carthage is a decentralized dependency manager that builds your dependencies and provides you with binary frameworks.

You can install Carthage with Homebrew using the following command:

$ brew update
$ brew install carthage

To integrate PSOperations into your Xcode project using Carthage, specify it in your Cartfile:

github "pluralsight/PSOperations"

Run carthage to build the framework and drag the built PSOperations.framework into your Xcode project. Optionally you can add PSOperationsHealth.framework, PSOperationsPassbook.framework, PSOperationsCalendar.framework and PSOperationsLocation.framework

Getting started

Don't forget to import!

import PSOperations

If you are using the HealthCapability, PassbookCapability, CalendarCapability, LocationCapability or LocationOperation you'll need to import them separately:

import PSOperationsHealth
import PSOperationsPassbook
import PSOperationsCalendar
import PSOperationsLocation

These features need to be in a separate framework otherwise they may cause App Store review rejection for importing HealthKit, PassKit, EventKit or CoreLocation but not actually using them.

Create a Queue

The OperationQueue is the heartbeat and is a subclass of NSOperationQueue:

let operationQueue = OperationQueue()

Create an Operation

Operation is a subclass of NSOperation. Like NSOperation it doesn't do much. But PSOperations provides a few helpful subclasses such as:

BlockOperation
GroupOperation
URLSessionTaskOperation
LocationOperation
DelayOperation

Here is a quick example:

let blockOperation = BlockOperation {
    print("perform operation")
}

operationQueue.addOperation(blockOperation)

Observe an Operation

Operation instances can be observed for starting, cancelling, finishing and producing new operations with the OperationObserver protocol.

PSOperations provide a couple of types that implement the protocol:

BlockObserver
TimeoutObserver

Here is a quick example:

let blockOperation = BlockOperation {
    print("perform operation")
}

let finishObserver = BlockObserver { operation, error in        
    print("operation finished! \(error)")
}

blockOperation.addObserver(finishObserver)

operationQueue.addOperation(blockOperation)

Set Conditions on an Operation

Operation instances can have conditions required to be met in order to execute using the OperationCondition protocol.

PSOperations provide a several types that implement the protocol:

SilentCondition
NegatedCondition
NoCancelledDependencies
MutuallyExclusive
ReachabilityCondition
Capability

Here is a quick example:

let blockOperation = BlockOperation {
    print("perform operation")
}

let dependentOperation = BlockOperation {
    print("working away")
}
                dependentOperation.addCondition(NoCancelledDependencies())
dependentOperation.addDependency(blockOperation)

operationQueue.addOperation(blockOperation)
operationQueue.addOperation(dependentOperation)

if blockOperation is cancelled, dependentOperation will not execute.

Set Capabilities on an Operation

A CapabilityType is used by the Capability condition and allows you to easily view the authorization state and request the authorization of certain capabilities within Apple's ecosystem. i.e. Calendar, Photos, iCloud, Location, and Push Notification.

Here is a quick example:

let blockOperation = BlockOperation {
    print("perform operation")
}

let calendarCapability = Capability(Photos())

blockOperation.addCondition(calendarCapability)

operationQueue.addOperation(blockOperation)

This operation requires access to Photos and will request access to them if needed.

Going custom

The examples above provide simple jobs but PSOperations can be involved in many parts of your application. Here is a custom UIStoryboardSegue that leverages the power of PSOperations. The segue is retained until an operation is completed. This is a generic OperationSegue that will run any given operation. One use case for this might be an authentication operation that ensures a user is authenticated before preceding with the segue. The authentication operation could even present authentication UI if needed.

class OperationSegue: UIStoryboardSegue {

    var operation: Operation?
    var segueCompletion: ((success: Bool) -> Void)?

    override func perform() {        
        if let operation = operation {
            let opQ = OperationQueue()
            var retainedSelf: OperationSegue? = self

            let completionObserver = BlockObserver {
                op, errors in

                dispatch_async_on_main {
                    defer {
                        retainedSelf = nil
                    }

                    let success = errors.count == 0 && !op.cancelled

                    if let completion = retainedSelf?.segueCompletion {
                        completion(success: success)
                    }

                    if success {
                        retainedSelf?.finish()
                    }
                }
            }

            operation.addObserver(completionObserver)
            opQ.addOperation(operation)
        } else {
            finish()
        }
    }

    func finish() {
        super.perform()
    }
}

Contribute

Feel free to submit pull requests, as we are always looking for improvements from the community.

WWDC Differences

Differences from the first version of the WWDC sample code:

Differences from the second version of the WWDC sample code:

A difference from the WWDC Sample code worth mentioning: