Flow 是一个工具/设计模式,帮助开发者编写简洁易读的代码。有两个主要关注点:操作流和数据流
使用 Flow,我们应该能够实现以下功能
Flow 引用了组合模式(https://en.wikipedia.org/wiki/Composite_pattern)和责任链模式(包括命令模式)(https://en.wikipedia.org/wiki/Chain-of-responsibility_pattern)
因此,我们将操作封装成对象,这些对象可以以树结构进行链接。每个操作都是独立的,但如果存在操作所需的数据,也能够与其他操作一起使用。
以下是一个简单使用的示例
@IBAction func simpleChainedFlow() {
Flow()
.add(operation: SimplePrintOp(message: "hello world"))
.add(operation: SimplePrintOp(message: "good bye"))
.setWillStartBlock(block: commonWillStartBlock())
.setDidFinishBlock(block: commonDidFinishBlock())
.start()
}
在这 5 行代码中,我们可以知道,两个操作将依次执行,并能在操作前后执行某些操作。
为了使逻辑可读,重要的是使操作名称具有意义。为操作命名是开发者的责任。名称还决定了代码的可复用程度。例如:如果您创建了一个名为 SimplePrintOp
的操作,它应只包含与它关联的消息输出版本。您不应在名称的上下文之外做任何事情。例如:将消息发送到服务器/写入文件。
此外,所有为 Flow 创建的操作应共享一个共同的后缀(例如 Op),以便所有开发者都能知道存在可复用的操作。
您可以使用 FlowArrayGroupDispatcher
运行一批操作。
Flow()
.setDataBucket(dataBucket: ["images": ["a", "b", "c", "d"]])
.addGrouped(operationCreator: UploadSingleImageOp.self, dispatcher: FlowArrayGroupDispatcher(inputKey: "images", outputKey: "imageURLs", maxConcurrentOperationCount: 3, allowFailure: false))
.start()
FlowArrayGroupDispatcher 将将目标数组数据桶中的数组发送到创建的操作,并传递必要的参数,并在之后收集结果。
import Flow_iOS
class UploadSingleImageOp: FlowOperation, FlowOperationCreator {
static func create() -> FlowOperation {
return UploadSingleImageOp()
}
override var primaryInputParamKey: String { return "targetImageForUpload" }
override var primaryOutputParamKey: String { return "uploadedImageUrl" }
override func mainLogic() {
guard let image: String = getData(name: primaryInputParamKey) else { return }
DispatchQueue.global().asyncAfter(deadline: .now() + .seconds(1)) {
self.log(message: "simulation of upload single image callback")
self.setData(name: self.primaryOutputParamKey, value: "url_of_" + image)
self.finishSuccessfully()
}
startWithAsynchronous()
}
}
对于上述示例,FlowArrayGroupDispatcher 会根据数据 bucket 中 images
数据的数组大小创建一组 UploadSingleImageOp。由于 UploadSingleImageOp 声明 targetImageForUpload
作为输入键,uploadedImageUrl
作为输出键。FlowArrayGroupDispatcher 将为每个 UploadSingleImageOp 创建临时数据 bucket 并在其中包含 targetImageForUpload
。如果操作成功,FlowArrayGroupDispatcher 将收集键为 uploadedImageUrl
的对象并放入结果数组 imageURLs
中。
在如此设计中,UploadSingleImageOp 可以 作为单一操作或分组操作重用
。
您也可以设置 maxConcurrentOperationCount(可选,默认=3)
以控制操作是逐个执行还是批量执行。如果将 allowFailure(可选,默认=false)
设置为 true,即使组中的某些/所有操作失败,Flow 也将继续运行。因此,输出数组可能比输入数组更短甚至是空的。
Flow 允许简单的案例处理。例如
@IBAction func demoCases() {
Flow()
.setDataBucket(dataBucket: ["images": ["a", "b", "c", "d", 1], "target": "A"])
.add(operation: SimplePrintOp(message: "Step1"))
.add(operation: SimplePrintOp(message: "Step2A1"), flowCase: FlowCase(key: "target", value: "A"))
.add(operation: SimplePrintOp(message: "Step2A2"))
.add(operation: SimplePrintOp(message: "Step2B1"), flowCase: FlowCase(key: "target", value: "B"))
.add(operation: SimplePrintOp(message: "Step2B2"))
.combine()
.add(operation: SimplePrintOp(message: "Step3"))
.setWillStartBlock(block: commonWillStartBlock())
.setDidFinishBlock(block: commonDidFinishBlock())
.start()
}
在 Step1
完成后,Flow 将根据数据 bucket中的 target
的值运行 Step2A1
分支或 Step2B1
分支。且使用 combine
将所有案例合并回单个节点 Step3
。
为了使蓝图可读,不支持的嵌套案例。同时,案例值类型必须是 String
。
import Flow_iOS
class MockAsyncLoginOp: FlowOperation {
override func mainLogic() {
guard let email: String = getData(name: "email") else { return }
guard let password: String = getData(name: "password") else { return }
DispatchQueue.global().asyncAfter(deadline: .now() + .seconds(1)) {
self.log(message: "simulation of login callback")
if email == "[email protected]" && password == "123456" {
let mockAccessToken = "sdaftadagasg"
self.setData(name: "accessToken", value: mockAccessToken)
self.finishSuccessfully()
} else {
let error = NSError(domain: "No such account", code: 404, userInfo: nil)
self.finishWithError(error: error)
}
}
startWithAsynchronous()
}
}
在示例中的 MockAsyncLoginOp 中,它需要从数据 bucket 中获取两个输入数据(email
和 password
)。Flow 将检查数据是否存在并且数据类型是否正确(即它们在此案例中必须是 String
)。如果没有找到具有正确类型的任何数据,操作将被标记为失败,Flow 将停止。您可以请求任何类型。例如,您在项目中有一个名为“LoginData”的类。
guard let loginData: LoginData = getData(name: "loginData") else { return }
创建操作很简单
FlowOperation
mainLogic
中放置您的逻辑finishSuccessfully
或 finishWithError
startWithAsynchronous
log
记录调试日志FlowOperationCreator
以使操作可在 Group
中使用。并重写 primaryInputParamKey
和 primaryOutputParamKey
一些示例
class SimplePrintOp: FlowOperation {
var message: String!
init(message: String) {
self.message = message
}
override func mainLogic() {
log(message: message)
finishSuccessfully()
}
}
class MockAsyncLoadProfileOp: FlowOperation {
override func mainLogic() {
guard let accessToken: String = getData(name: "accessToken") else { return }
DispatchQueue.global().asyncAfter(deadline: .now() + .seconds(1)) {
self.log(message: "simulation of success load profile callback")
self.setData(name: "profileRefreshDate", value: Date())
self.finishSuccessfully()
}
startWithAsynchronous()
}
}
class UploadSingleImageOp: FlowOperation, FlowOperationCreator {
static func create() -> FlowOperation {
return UploadSingleImageOp()
}
override var primaryInputParamKey: String { return "targetImageForUpload" }
override var primaryOutputParamKey: String { return "uploadedImageUrl" }
override func mainLogic() {
guard let image: String = getData(name: primaryInputParamKey) else { return }
DispatchQueue.global().asyncAfter(deadline: .now() + .seconds(1)) {
self.log(message: "simulation of upload single image callback")
self.setData(name: self.primaryOutputParamKey, value: "url_of_" + image)
self.finishSuccessfully()
}
startWithAsynchronous()
}
}
您可以设置 WillStartBlock
和 DidFinishBlock
来在 Flow 运行之前或之后被通知。它们在 主线程
中调用,因此您可以进行 UI 变更。在块中使用 Flow 实例,您可以访问 dataBucket: [String: Any]
,isSuccess: Bool
和 error: Error?
,并执行您的处理。
建议创建通用的处理块,以进一步简化您的蓝图。
func commonWillStartBlock(block: FlowWillStartBlock? = nil) -> FlowWillStartBlock {
let result: FlowWillStartBlock = {
flow in
block?(flow)
self.summaryTextView.text = "Flow Starting..."
}
return result
}
func commonDidFinishBlock(block: FlowDidFinishBlock? = nil) -> FlowDidFinishBlock {
let result: FlowDidFinishBlock = {
flow in
block?(flow)
self.summaryTextView.text = flow.generateSummary()
}
return result
}
这是我创建 Flow 时的最喜爱功能之一。开发者总是很难跟踪控制台日志,因为控制台中有很多不受欢迎的日志。更糟的是,在一系列异步操作中。Flow 将捕获操作中发送的所有日志,并在最后为您生成摘要。在完成块中调用 flow.generateSummary()
。
例如
====== Flow Summary ======
4:17:18 PM [DataBucket] start with:
password: 123456
email: [email protected]
4:17:18 PM [MockAsyncLoginOp] WillStart
4:17:19 PM [MockAsyncLoginOp] simulation of login callback
4:17:19 PM [DataBucket] add for accessToken: sdaftadagasg
4:17:19 PM [MockAsyncLoginOp] DidFinish: successfully
4:17:19 PM [MockAsyncLoadProfileOp] WillStart
4:17:20 PM [MockAsyncLoadProfileOp] simulation of success load profile callback
4:17:20 PM [DataBucket] add for profileRefreshDate: 2017-07-13 08:17:20 +0000
4:17:20 PM [MockAsyncLoadProfileOp] DidFinish: successfully
4:17:20 PM [DataBucket] end with:
profileRefreshDate: 2017-07-13 08:17:20 +0000
email: [email protected]
accessToken: sdaftadagasg
password: 123456
Flow isSuccess: true
====== Ending ======
====== Flow Summary ======
4:17:06 PM [DataBucket] start with:
password: 123456
email_address: [email protected]
4:17:06 PM [MockAsyncLoginOp] WillStart
4:17:06 PM [MockAsyncLoginOp] DidFinish: withInsufficientInputData: can't find <email> in data bucket
4:17:06 PM [DataBucket] end with:
password: 123456
email_address: [email protected]
Flow isSuccess: false
====== Ending ======
您可以在一个地方追踪数据变化,查看操作如何执行。如果需要,您可以将总结字符串发送到您的服务器。
当然,在某个方面 RxSwift 更强大。但我认为,如果我们能使我们的代码:简单易懂
,使用 流程
,即使是代码审核人员和非程序员也能理解蓝图中您的逻辑。
Swift 3.2 iOS 8.0
Flow 可以通过 CocoaPods 获取。
pod "Flow-iOS"
导入
import Flow_iOS
Roy Ng,[email protected] @ Redso,https://www.redso.com.hk/
领英: https://www.linkedin.com/in/roy-ng-19427735/
Flow 在 MIT 许可证下可用。有关更多信息,请参阅 LICENSE 文件。