Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Advanced Operations in Swift

License

NotificationsYou must be signed in to change notification settings

ProcedureKit/ProcedureKit

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Build statusCoverage StatusDocumentationCocoaPods CompatiblePlatformCarthage compatible

ProcedureKit

A Swift framework inspired by WWDC 2015 Advanced NSOperations session. Previously known asOperations, developed by@danthorpe with a lot of help from our fantastic community.

ResourceWhere to find it
Session videodeveloper.apple.com
Old but more complete reference documentationdocs.danthorpe.me/operations
Updated but not yet complete reference docsprocedure.kit.run/development
Programming guideoperations.readme.io

Compatibility

ProcedureKit supports all current Apple platforms. The minimum requirements are:

  • iOS 9.0+
  • macOS 10.11+
  • watchOS 3.0+
  • tvOS 9.2+

The current released version of ProcedureKit (5.1.0) supports Swift 4.2+ and Xcode 10.1. Thedevelopment branch is Swift 5 and Xcode 10.2 compatible.

Framework structure

ProcedureKit is a "multi-module" framework (don't bother Googling that, I just made it up). What I mean, is that the Xcode project has multiple targets/products each of which produces a Swift module. Some of these modules are cross-platform, others are dedicated, e.g.ProcedureKitNetwork vsProcedureKitMobile.

Installing ProcedureKit

See theInstalling ProcedureKit guide.

Usage

Procedure is aFoundation.Operation subclass. It is an abstract class whichmust be subclassed.

import ProcedureKitclassMyFirstProcedure:Procedure{overridefunc execute(){print("Hello World")finish()}}letqueue=ProcedureQueue()letmyProcedure=MyFirstProcedure()queue.add(procedure: myProcedure)

the key points here are:

  1. SubclassProcedure
  2. Overrideexecute but do not callsuper.execute()
  3. Always callfinish() after thework is done, or if the procedure is cancelled. This could be done asynchronously.
  4. Add procedures to instances ofProcedureQueue.

Observers

Observers are attached to aProcedure subclass. They receive callbacks when lifecycle events occur. The lifecycle events are:did attach,will execute,did execute,did cancel,will add new operation,did add new operation,will finish anddid finish.

These methods are defined by a protocol, so custom classes can be written to conform to multiple events. However, block based methods exist to add observers more naturally. For example, to observe when a procedure finishes:

myProcedure.addDidFinishBlockObserver{ procedure, errorsin     procedure.log.info(message:"Yay! Finished!")}

The framework also providesBackgroundObserver,TimeoutObserver andNetworkObserver.

See the wiki on [[Observers|Observers]] for more information.

Conditions

Conditions are attached to aProcedure subclass. Before a procedure is ready to execute it will asynchronouslyevaluate all of its conditions. If any condition fails, it finishes with an error instead of executing. For example:

myProcedure.add(condition:BlockCondition{     // procedure will execute if true    // procedure will be ignored if false    // procedure will fail if error is thrownreturn trueOrFalse // or throw AnError()}

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 andUserConfirmationCondition (inProcedureKitMobile).

See the wiki on [[Conditions|Conditions]], or the old programming guide onConditions| for more information.

Capabilities

Acapability represents the application’s ability to access device or user account abilities, or potentially any kind of gated resource. For example, location services, cloud kit containers, calendars etc or a webservice. TheCapabiltiyProtocol provides a unified model to:

  1. Check the current authorization status, usingGetAuthorizationStatusProcedure,
  2. Explicitly request access, usingAuthorizeCapabilityProcedure
  3. Both of the above as a condition calledAuthorizedFor.

For example:

import ProcedureKitimport ProcedureKitLocationclassDoSomethingWithLocation:Procedure{overrideinit(){        super.init()        name="Location Operation"add(condition:AuthorizedFor(Capability.Location(.whenInUse)))}overridefunc execute(){        // do something with Location Services herefinish()}}

ProcedureKit provides the following capabilities:Capability.CloudKit andCapability.Location.

InOperations, (a previous version of this framework), more functionality existed (calendar, health, photos, address book, etc), and we are still considering how to offer these inProcedureKit.

See the wiki on [[Capabilities|Capabilities]], or the old programming guide onCapabilities for more information.

Logging

Procedure has its own internal logging functionality exposed via alog property:

classLogExample:Procedure{overridefunc execute(){        log.info("Hello World!")finish()}}

See the programming guide for more information onlogging andsupporting 3rd party log frameworks.

Dependency Injection

Often, procedures will need dependencies in order to execute. As is typical with asynchronous/event based applications, these dependencies might not be known at creation time. Instead they must be injected after the procedure is initialised, but before it is executed.ProcedureKit supports this via a set of protocols and types which work together. We think this pattern is great, as it encourages the composition of small single purpose procedures. These can be easier to test and potentially enable greater re-use. You will find dependency injection used and encouraged throughout this framework.

Anyway, firstly, a value may be ready or pending. For example, when a procedure is initialised, it might not have all its dependencies, so they are in a pending state. Hopefully they become ready by the time it executes.

Secondly, if a procedure is acquiring the dependency required by another procedure, it may succeed, or it may fail with an error. Therefore there is a simpleResult type which supports this.

Thirdly, there are protocols to define theinput andoutput properties.

InputProcedure associates anInput type. AProcedure subclass can conform to this to allow dependency injection. Note, that only oneinput property is supported, therefore, create intermediate struct types to contain multiple dependencies. Of course, theinput property is a pending value type.

OutputProcedure exposes theOutput associated type via itsoutput property, which is a pending result type.

Bringing it all together is a set of APIs onInputProcedure which allows chaining dependencies together. Like this:

import ProcedureKitLocation// This class is part of the framework, it // conforms to OutputProcedureletgetLocation=UserLocationProcedure()// Lets assume we've written this, it// conforms to InputProcedureletprocessLocation=ProcessUserLocation()// This line sets up dependency & injection// it automatically handles errors and cancellationprocessLocation.injectResult(from: getLocation)// Still need to add both procedures to the queuequeue.add(procedures: getLocation, processLocation)

In the above, it is assumed that theInput type matched theOutput type, in this case,CLLocation. However, it is also possible to use a closure to massage the output type to the required input type, for example:

import ProcedureKitLocation// This class is part of the framework, it // conforms to OutputProcedureletgetLocation=UserLocationProcedure()// Lets assume we've written this, it// conforms to InputProcedure, and // requires a CLLocationSpeed valueletprocessSpeed=ProcessUserSpeed()// This line sets up dependency & injection// it automatically handles errors and cancellation// and the closure extracts the speed valueprocessLocation.injectResult(from: getLocation){ $0.speed}// Still need to add both procedures to the queuequeue.add(procedures: getLocation, processLocation)

Okay, so what just happened? Well, theinjectResult API has a variant which accepts a trailing closure. The closure receives the output value, and must return the input value (or throw an error). So,{ $0.speed } will return the speed property from the user'sCLLocation instance.

Key thing to note here is that this closure runs synchronously. So, it's best to not put anything onerous onto it. If you need to do more complex data mappings, check outTransformProcedure andAsyncTransformProcedure.

See the programming guide onInjecting Results for more information.

Packages

No packages published

Contributors34

Languages


[8]ページ先頭

©2009-2025 Movatter.jp