All Projects → ProcedureKit → Procedurekit

ProcedureKit / Procedurekit

Licence: mit
Advanced Operations in Swift

Programming Languages

swift
15916 projects

Projects that are alternatives of or similar to Procedurekit

Difference
Simple way to identify what is different between 2 instances of any type. Must have for TDD.
Stars: ✭ 837 (-3.01%)
Mutual labels:  tvos, watchos
Sdwebimageswiftui
SwiftUI Image loading and Animation framework powered by SDWebImage
Stars: ✭ 844 (-2.2%)
Mutual labels:  tvos, watchos
Purchases Ios
In-app purchases and subscriptions made easy. iOS, MacOS, iPadOS, tvOS, and WatchOS support.
Stars: ✭ 614 (-28.85%)
Mutual labels:  tvos, watchos
Open Source Ios Apps
📱 Collaborative List of Open-Source iOS Apps
Stars: ✭ 28,826 (+3240.21%)
Mutual labels:  tvos, watchos
Swiftui
A collaborative list of awesome SwiftUI resources. Feel free to contribute!
Stars: ✭ 774 (-10.31%)
Mutual labels:  tvos, watchos
Countly Sdk Ios
Countly Product Analytics iOS SDK with macOS, watchOS and tvOS support.
Stars: ✭ 585 (-32.21%)
Mutual labels:  tvos, watchos
Swiftyrsa
RSA public/private key encryption in Swift
Stars: ✭ 894 (+3.59%)
Mutual labels:  tvos, watchos
Swiftyutils
All the reusable code that we need in each project
Stars: ✭ 490 (-43.22%)
Mutual labels:  tvos, watchos
Flexibleimage
A simple way to play with the image!
Stars: ✭ 798 (-7.53%)
Mutual labels:  tvos, watchos
Apprepositorytemplate
The easiest way to start a new application project without any manual configuration
Stars: ✭ 24 (-97.22%)
Mutual labels:  tvos, watchos
Rome
Carthage cache for S3, Minio, Ceph, Google Storage, Artifactory and many others
Stars: ✭ 724 (-16.11%)
Mutual labels:  tvos, watchos
Ios Cmake
A CMake toolchain file for iOS, macOS, watchOS & tvOS C/C++/Obj-C++ development
Stars: ✭ 844 (-2.2%)
Mutual labels:  tvos, watchos
Xcake
🍰 Describe Xcode projects in a human readable format and (re)generate one on demand.
Stars: ✭ 549 (-36.38%)
Mutual labels:  tvos, watchos
Dynamicjson
Access JSON properties dynamically like JavaScript using Swift 4.2's new @dynamicMemberLookup feature
Stars: ✭ 678 (-21.44%)
Mutual labels:  tvos, watchos
Swiftframeworktemplate
A template for new Swift iOS / macOS / tvOS / watchOS Framework project ready with travis-ci, cocoapods, Carthage, SwiftPM and a Readme file
Stars: ✭ 527 (-38.93%)
Mutual labels:  tvos, watchos
Samkeychain
Simple Objective-C wrapper for the keychain that works on Mac and iOS
Stars: ✭ 5,389 (+524.45%)
Mutual labels:  tvos, watchos
Gridstack
A flexible grid layout view for SwiftUI
Stars: ✭ 474 (-45.08%)
Mutual labels:  tvos, watchos
Corexlsx
Excel spreadsheet (XLSX) format parser written in pure Swift
Stars: ✭ 481 (-44.26%)
Mutual labels:  tvos, watchos
Flint
The Flint framework for building apps on Apple platforms using Feature Driven Development
Stars: ✭ 636 (-26.3%)
Mutual labels:  tvos, watchos
Guitar
A Cross-Platform String and Regular Expression Library written in Swift.
Stars: ✭ 641 (-25.72%)
Mutual labels:  tvos, watchos

Build status Coverage Status Documentation CocoaPods Compatible Platform Carthage compatible

ProcedureKit

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

Resource Where to find it
Session video developer.apple.com
Old but more complete reference documentation docs.danthorpe.me/operations
Updated but not yet complete reference docs procedure.kit.run/development
Programming guide operations.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. The development 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 vs ProcedureKitMobile.

Installing ProcedureKit

See the Installing ProcedureKit guide.

Usage

Procedure is a Foundation.Operation subclass. It is an abstract class which must be subclassed.

import ProcedureKit

class MyFirstProcedure: Procedure {
    override func execute() {
        print("Hello World")
        finish()
    }
}

let queue = ProcedureQueue()
let myProcedure = MyFirstProcedure()
queue.add(procedure: myProcedure)

the key points here are:

  1. Subclass Procedure
  2. Override execute but do not call super.execute()
  3. Always call finish() after the work is done, or if the procedure is cancelled. This could be done asynchronously.
  4. Add procedures to instances of ProcedureQueue.

Observers

Observers are attached to a Procedure 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 and did 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, errors in 
    procedure.log.info(message: "Yay! Finished!")
}

The framework also provides BackgroundObserver, TimeoutObserver and NetworkObserver.

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

Conditions

Conditions are attached to a Procedure subclass. Before a procedure is ready to execute it will asynchronously evaluate 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 thrown
    return 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 and UserConfirmationCondition (in ProcedureKitMobile).

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

Capabilities

A capability 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. The CapabiltiyProtocol provides a unified model to:

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

For example:

import ProcedureKit
import ProcedureKitLocation

class DoSomethingWithLocation: Procedure {
    override init() {
        super.init()
        name = "Location Operation"
        add(condition: AuthorizedFor(Capability.Location(.whenInUse)))
    }
   
    override func execute() {
        // do something with Location Services here
        
        
        finish()
    }
}

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

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

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

Logging

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

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

See the programming guide for more information on logging and supporting 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 simple Result type which supports this.

Thirdly, there are protocols to define the input and output properties.

InputProcedure associates an Input type. A Procedure subclass can conform to this to allow dependency injection. Note, that only one input property is supported, therefore, create intermediate struct types to contain multiple dependencies. Of course, the input property is a pending value type.

OutputProcedure exposes the Output associated type via its output property, which is a pending result type.

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

import ProcedureKitLocation

// This class is part of the framework, it 
// conforms to OutputProcedure
let getLocation = UserLocationProcedure()

// Lets assume we've written this, it
// conforms to InputProcedure
let processLocation = ProcessUserLocation()

// This line sets up dependency & injection
// it automatically handles errors and cancellation
processLocation.injectResult(from: getLocation)

// Still need to add both procedures to the queue
queue.add(procedures: getLocation, processLocation)

In the above, it is assumed that the Input type matched the Output 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 OutputProcedure
let getLocation = UserLocationProcedure()

// Lets assume we've written this, it
// conforms to InputProcedure, and 
// requires a CLLocationSpeed value
let processSpeed = ProcessUserSpeed()

// This line sets up dependency & injection
// it automatically handles errors and cancellation
// and the closure extracts the speed value
processLocation.injectResult(from: getLocation) { $0.speed }

// Still need to add both procedures to the queue
queue.add(procedures: getLocation, processLocation)

Okay, so what just happened? Well, the injectResult 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's CLLocation 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 out TransformProcedure and AsyncTransformProcedure.

See the programming guide on Injecting Results for more information.

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].