ProcedureKit 5.2.0

ProcedureKit 5.2.0

测试已测试
语言 SwiftSwift
许可证 MIT
发布最新发布2019 年 4 月
SPM支持 SPM

Dan Thorpe 维护。



  • ProcedureKit 核心贡献者

Build status Coverage Status Documentation CocoaPods Compatible Platform Carthage compatible

ProcedureKit

这是一个受 WWDC 2015 Advanced NSOperations 会话启发的 Swift 框架。之前被称为 Operations,由 @danthorpe 开发,并得到了我们出色的社区的大力支持。

资源 在哪里可以找到
会话视频 developer.apple.com
旧但更完整的参考文档 docs.danthorpe.me/operations
更新但尚未完成的参考文档 procedure.kit.run/development
编程指南 operations.readme.io

兼容性

ProcedureKit 支持所有当前 Apple 平台。最低要求是

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

当前发布的 ProcedureKit 版本(5.1.0)支持 Swift 4.2+ 和 Xcode 10.1。《开发》分支与 Swift 5 和 Xcode 10.2 兼容。

框架结构

ProcedureKit 是一个 "多模块" 框架(不要费心去谷歌搜索这个名词,我只是自己定义的)。我的意思是,Xcode 项目有多达多个目标/产品,每个目标/产品都会生成一个 Swift 模块。其中一些模块是跨平台的,其他的则针对特定平台,例如 ProcedureKitNetworkProcedureKitMobile

安装ProcedureKit

请参阅安装ProcedureKit指南。

用法

ProcedureFoundation.Operation子类。它是一个抽象类,必须被继承。

import ProcedureKit

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

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

这里的关键点是

  1. 继承Procedure
  2. 重写execute但不调用super.execute()
  3. 在所有工作完成后或在流程被取消后,始终调用finish()。这可以异步完成。
  4. 将流程添加到ProcedureQueue实例中。

观察者

观察者附加到Procedure子类。当生命周期事件发生时,它们会接收回调。生命周期事件包括:did attachwill executedid executedid cancelwill add new operationdid add new operationwill finishdid finish

这些方法通过协议定义,因此可以编写自定义类以符合多个事件。但是,存在基于块的方法来更自然地添加观察者。例如,观察当流程完成时

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

框架还提供了BackgroundObserverTimeoutObserverNetworkObserver

有关更多信息,请参阅关于[[观察者|Observer]]的wiki。

条件

条件附加到Procedure子类。在流程准备执行之前,它会异步地异步评估它的所有条件。如果有任何条件失败,它将结束并显示错误,而不是执行。例如

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()
}

条件可以相互排斥。这类似于一个锁定阻止执行具有相同排斥的其他操作。

框架提供了以下条件:AuthorizedForBlockConditionMutuallyExclusiveNegatedConditionNoFailedDependenciesConditionSilentConditionUserConfirmationCondition(在ProcedureKitMobile中)。

有关更多信息,请参阅关于[[条件|Conditions]]的wiki,或查看旧编程指南中的条件|

能力

“能力”代表了应用访问设备或用户账户能力,或者可能是任何形式的受控资源的能力。例如,位置服务、云容器、日历等或者是网络服务。《CapabiltiyProtocol》提供了一致的模型来

  1. 使用GetAuthorizationStatusProcedure检查当前授权状态
  2. 使用AuthorizeCapabilityProcedure明确请求访问
  3. 上述两项作为一个名为AuthorizedFor的条件。

例如

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提供了以下能力:Capability.CloudKitCapability.Location

Operations(本框架的先前版本)中,存在更多的功能(如日历、健康、照片、通讯录等),我们仍在考虑如何在ProcedureKit中提供这些功能。

有关更多信息,请参阅关于[[能力|能力]]的wiki,或者有关能力的旧编程指南。

日志记录

Procedure有自己内部的日志功能,通过log属性公开

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

有关更多信息,请参阅有关日志记录支持第三方日志框架的编程指南。

依赖注入

通常,程序在执行前需要依赖项。在异步/事件特性的应用中,这些依赖项可能在创建时并不知道。因此,它们必须在程序初始化后,但在执行前进行注入。《ProcedureKit》通过一系列协议和类型支持此操作。我们认为这种模式很棒,因为它鼓励小型单一用途程序的组合。这些程序可能更容易测试,并可能实现更大的重用。你会发现,在整个框架中都要使用并鼓励这种依赖注入。

首先,一个值可能是已准备或待定。例如,当程序初始化时,它可能没有所有的依赖项,因此它们处于待定状态。希望它们能在执行时变为准备就绪。

其次,如果程序正在获取另一个程序所需的依赖项,它可能成功,也可能由于错误而失败。因此,有一个简单的Result类型来支持这一点。

第三,有协议来定义inputoutput属性。

《InputProcedure》将一个 Input 类型关联。一个 Procedure 子类可以遵守这个协议,以便进行依赖注入。请注意,仅支持一个 input 属性,因此,创建中间结构类型来包含多个依赖项。当然,input 属性是一个挂起值类型。

《OutputProcedure》通过它的 output 属性公开关联的 Output 类型,这是一个挂起结果类型。

将所有这些整合在一起的是一组在 InputProcedure 上的 API,它允许将依赖项连接起来。如下所示

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)

在上面的例子中,假设 Input 类型与 Output 类型匹配,在这种情况下,是 CLLocation。然而,也可以使用闭包将输出类型调整为所需的输入类型,例如

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)

那么刚刚发生了什么呢?好的,injectResult API 有一个可以接受尾部闭包的变体。闭包接收输出值,必须返回输入值(或者抛出错误)。所以,'{ $0.speed }'将返回用户 CLLocation 实例的速度属性。

这里需要注意的是,此闭包是同步运行的。因此,最好不要在它上面放置繁重的任务。如果您需要进行更复杂的数据映射,请检查TransformProcedureAsyncTransformProcedure

请参阅有关注入结果的编程指南,以获取更多信息。