{"id":13907927,"url":"https://github.com/ProcedureKit/ProcedureKit","last_synced_at":"2025-07-18T06:32:05.800Z","repository":{"id":34243143,"uuid":"38116404","full_name":"ProcedureKit/ProcedureKit","owner":"ProcedureKit","description":"Advanced Operations in Swift","archived":false,"fork":false,"pushed_at":"2022-12-08T12:50:31.000Z","size":12093,"stargazers_count":895,"open_issues_count":65,"forks_count":132,"subscribers_count":40,"default_branch":"development","last_synced_at":"2024-11-24T19:50:02.158Z","etag":null,"topics":["asynchronous-programming","ios","macos","nsoperation","procedurekit","swift","tvos","watchos"],"latest_commit_sha":null,"homepage":"http://procedure.kit.run/development/","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ProcedureKit.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":".github/CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2015-06-26T14:44:38.000Z","updated_at":"2024-11-22T10:15:58.000Z","dependencies_parsed_at":"2022-09-14T02:01:03.670Z","dependency_job_id":null,"html_url":"https://github.com/ProcedureKit/ProcedureKit","commit_stats":null,"previous_names":[],"tags_count":72,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ProcedureKit%2FProcedureKit","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ProcedureKit%2FProcedureKit/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ProcedureKit%2FProcedureKit/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ProcedureKit%2FProcedureKit/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ProcedureKit","download_url":"https://codeload.github.com/ProcedureKit/ProcedureKit/tar.gz/refs/heads/development","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":226218879,"owners_count":17591637,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["asynchronous-programming","ios","macos","nsoperation","procedurekit","swift","tvos","watchos"],"created_at":"2024-08-06T23:02:19.107Z","updated_at":"2024-11-25T16:31:12.236Z","avatar_url":"https://github.com/ProcedureKit.png","language":"Swift","readme":"![](https://raw.githubusercontent.com/ProcedureKit/ProcedureKit/development/header.png)\n\n[![Build status](https://badge.buildkite.com/4bc80b0824c6357ae071342271cb503b8994cf0cfa58645849.svg)](https://buildkite.com/procedurekit/procedurekit)\n[![Coverage Status](https://coveralls.io/repos/github/ProcedureKit/ProcedureKit/badge.svg?branch=swift%2F2.2)](https://coveralls.io/github/ProcedureKit/ProcedureKit?branch=swift%2F2.2)\n[![Documentation](http://procedure.kit.run/development/badge.svg)](http://procedure.kit.run/development)\n[![CocoaPods Compatible](https://img.shields.io/cocoapods/v/ProcedureKit.svg?style=flat)](https://cocoapods.org/pods/ProcedureKit)\n[![Platform](https://img.shields.io/cocoapods/p/ProcedureKit.svg?style=flat)](http://cocoadocs.org/docsets/ProcedureKit)\n[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)\n\n# ProcedureKit\n\nA Swift framework inspired by WWDC 2015 Advanced NSOperations session. Previously known as _Operations_, developed by [@danthorpe](https://github.com/danthorpe) with a lot of help from our fantastic community.\n\nResource | Where to find it\n---------|-----------------\nSession video | [developer.apple.com](https://developer.apple.com/videos/wwdc/2015/?id=226)\nOld but more complete reference documentation | [docs.danthorpe.me/operations](http://docs.danthorpe.me/operations/2.9.0/index.html)\nUpdated but not yet complete reference docs | [procedure.kit.run/development](http://procedure.kit.run/development/index.html)\nProgramming guide | [operations.readme.io](https://operations.readme.io)\n\n## Compatibility\n\nProcedureKit supports all current Apple platforms. The minimum requirements are:\n\n- iOS 9.0+\n- macOS 10.11+\n- watchOS 3.0+\n- tvOS 9.2+\n\nThe 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.\n\n## Framework structure\n\n_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`.\n\n## Installing ProcedureKit\n\nSee the [Installing ProcedureKit](http://procedure.kit.run/development/installing-procedurekit.html) guide. \n\n## Usage\n\n`Procedure` is a `Foundation.Operation` subclass. It is an abstract class which _must_ be subclassed.\n\n```swift\nimport ProcedureKit\n\nclass MyFirstProcedure: Procedure {\n    override func execute() {\n        print(\"Hello World\")\n        finish()\n    }\n}\n\nlet queue = ProcedureQueue()\nlet myProcedure = MyFirstProcedure()\nqueue.add(procedure: myProcedure)\n```\n\nthe key points here are:\n\n1. Subclass `Procedure`\n2. Override `execute` but do not call `super.execute()`\n4. Always call `finish()` after the *work* is done, or if the procedure is cancelled. This could be done asynchronously.\n5. Add procedures to instances of `ProcedureQueue`.\n\n## Observers\n\nObservers 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*.\n\nThese 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:\n\n```swift\nmyProcedure.addDidFinishBlockObserver { procedure, errors in \n    procedure.log.info(message: \"Yay! Finished!\")\n}\n```\n\nThe framework also provides `BackgroundObserver`, `TimeoutObserver` and `NetworkObserver`.\n\nSee the wiki on [[Observers|Observers]] for more information.\n\n## Conditions\n\nConditions 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:\n\n```swift\nmyProcedure.add(condition: BlockCondition { \n    // procedure will execute if true\n    // procedure will be ignored if false\n    // procedure will fail if error is thrown\n    return trueOrFalse // or throw AnError()\n}\n``` \n\nConditions can be mutually exclusive. This is akin to a lock being held preventing other operations with the same exclusion being executed.\n\nThe framework provides the following conditions: `AuthorizedFor`, `BlockCondition`, `MutuallyExclusive`, `NegatedCondition`, `NoFailedDependenciesCondition`, `SilentCondition` and `UserConfirmationCondition` (in _ProcedureKitMobile_).\n\nSee the wiki on [[Conditions|Conditions]], or the old programming guide on [Conditions|](https://operations.readme.io/docs/conditions) for more information.\n\n## Capabilities\n\nA _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:\n \n1. Check the current authorization status, using `GetAuthorizationStatusProcedure`, \n2. Explicitly request access, using `AuthorizeCapabilityProcedure`\n3. Both of the above as a condition called `AuthorizedFor`. \n\nFor example:\n\n```swift\nimport ProcedureKit\nimport ProcedureKitLocation\n\nclass DoSomethingWithLocation: Procedure {\n    override init() {\n        super.init()\n        name = \"Location Operation\"\n        add(condition: AuthorizedFor(Capability.Location(.whenInUse)))\n    }\n   \n    override func execute() {\n        // do something with Location Services here\n        \n        \n        finish()\n    }\n}\n```\n\n_ProcedureKit_ provides the following capabilities: `Capability.CloudKit` and `Capability.Location`.\n\nIn _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_. \n\nSee the wiki on [[Capabilities|Capabilities]], or the old programming guide on [Capabilities](https://operations.readme.io/docs/capabilities) for more information.\n\n## Logging\n\n`Procedure` has its own internal logging functionality exposed via a `log` property:\n\n```swift\nclass LogExample: Procedure {\n   \n    override func execute() {\n        log.info(\"Hello World!\")\n        finish()\n    }\n}\n```\n\nSee the programming guide for more information on [logging](https://operations.readme.io/docs/logging) and [supporting 3rd party log frameworks](https://operations.readme.io/docs/custom-logging).\n\n## Dependency Injection\n\nOften, 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. \n\nAnyway, 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.\n\nSecondly, 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.\n\nThirdly, there are protocols to define the `input` and `output` properties. \n\n`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.\n\n`OutputProcedure` exposes the `Output` associated type via its `output` property, which is a pending result type.\n\nBringing it all together is a set of APIs on `InputProcedure` which allows chaining dependencies together. Like this:\n\n```swift\nimport ProcedureKitLocation\n\n// This class is part of the framework, it \n// conforms to OutputProcedure\nlet getLocation = UserLocationProcedure()\n\n// Lets assume we've written this, it\n// conforms to InputProcedure\nlet processLocation = ProcessUserLocation()\n\n// This line sets up dependency \u0026 injection\n// it automatically handles errors and cancellation\nprocessLocation.injectResult(from: getLocation)\n\n// Still need to add both procedures to the queue\nqueue.add(procedures: getLocation, processLocation)\n```\n\nIn 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:\n\n```swift\nimport ProcedureKitLocation\n\n// This class is part of the framework, it \n// conforms to OutputProcedure\nlet getLocation = UserLocationProcedure()\n\n// Lets assume we've written this, it\n// conforms to InputProcedure, and \n// requires a CLLocationSpeed value\nlet processSpeed = ProcessUserSpeed()\n\n// This line sets up dependency \u0026 injection\n// it automatically handles errors and cancellation\n// and the closure extracts the speed value\nprocessLocation.injectResult(from: getLocation) { $0.speed }\n\n// Still need to add both procedures to the queue\nqueue.add(procedures: getLocation, processLocation)\n```\n\nOkay, 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.\n\nKey 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`](https://github.com/ProcedureKit/ProcedureKit/blob/development/Sources/ProcedureKit/Transform.swift#L7) and [`AsyncTransformProcedure`](https://github.com/ProcedureKit/ProcedureKit/blob/development/Sources/ProcedureKit/Transform.swift#L31). \n\nSee the programming guide on [Injecting Results](https://operations.readme.io/docs/injecting-results) for more information.\n","funding_links":[],"categories":["HarmonyOS"],"sub_categories":["Windows Manager"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FProcedureKit%2FProcedureKit","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FProcedureKit%2FProcedureKit","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FProcedureKit%2FProcedureKit/lists"}