什么是Flask Reactor?
Flask是Swift中单向数据流架构的多平台(iOS | OSX | tvOS)实现,它通过直观的API提供了一组独特的功能,这些功能超越了任何类似框架。
虽然Flux架构很抽象,但解释Flask却很容易
Reactors 允许您 Mix Substances 并对其 State Changes 进行 React
很简单对吧?
为了保持这种自然直觉,《Flux Stores》被称为 Substance
。在Flask中,一个 Substance
将代表您应用程序中的任何同质数据结构(例如,一个源、设置或应用程序本身)。
Flask允许通过统一的架构实现Redux和Fluxor模式的样式,尽管这种集成可能听起来很复杂,但使用Flask它也很容易表达。
Flask让您能够创建对称为 Mixers 的环境事件做出反应的 ReactiveSubstances,以及意为在特定 Reactor 中被 Mixed 的普通 Substances。
这是在 Fluxor 模式中的 Reactive Stores
作为 ReactiveSubstance
和 Redux 模式中的 Store Reducers
作为普通 Substance
之间的一种直接类比。
使用化学语义是接近flux-reactive模式的一种直观方法。毕竟,《React这个词在化学领域中本身就有定义。
目录
动机
Flask Reactor 是一个综合工具箱的基础,定义了“Flask 模式”作为在 Apple 的 Swift 平台上创建响应式应用的方式。因此,Flask 通过直观的 API 提供了最强大的功能集。
Flask 不仅超越了响应式模式的创新,还使用直观且易于实现的物理世界类比来开发表达式 API。与此同时,核心技术提供了 Redux 和 Fluxor 模式的统一实现,并提供了其他框架(如 async-flux、锁和嵌套键支持)中不可用的先进功能。这些功能是开发高级框架(如 FlaskNavigation 路由器或 FlaskAPI 管理器)所必需的。
尽管 Flask 功能强大,但其实现与最流行的框架(例如 ReSwift)一样简单。代码复杂度取决于您自己的实现,是可选的和渐进式的。
为什么选择 Flask?
Flask 提供了类似框架中找不到的独特功能
- 链式突变
- 捆绑多个存储
- 使用嵌套键的 Struct reductions
- 使用
FlaskNSRef
实现 NSObject 指针变化减少 - 支持嵌套键的 NSDictionary 和 Dictionary 减少
- Flux 锁和独占分发
- 自动存档到
UserDefaults
- 自动处理的管理附件
- 友好的高级 API
- 访问低级 API 以实现更细粒度的控制
- 混合使用 Redux 和 Fluxor 模式
CocoaPods
如果您使用 CocoaPods,只需将 Flask 添加到您的 Podfile
。
pod 'Flask'
然后导入模块
import Flask
架构
上面的流程图是 Flask 管道的高级概述。
- 绿色是
Reactor
组件 - 蓝色是
Flux
组件 - 粉色是
Substance
组件 - 黄色是
State
表示
如何工作?
这些是用于与 Flask 交互的主要组件
- 物质:您应用程序中任何数据结构
- 混合器:定义数据变更是事务闭包
- 反应器:将物质混合以产生反应
- 通量:连接管道的单向总线
组件
Flask API
以下是一些您将更频繁使用的 API 高级方法
// Managing your reactor instance
Flask.attachReactor(to:mixing:)
Flask.detachReactor(from:)
Flask.getReactor(at:)
// Dispatching a substancer mixer
Flask.substances(reactTo:payload:)
// Creating non-reactive substances
Flask.newSubstance(definedBy:)
Flask.newSubstance(definedBy:named:archive:)
查看这些方法以了解更多关于低级 API 的信息。
物质
物质在最小化初始化时带有 State
结构。反应性物质需要一个额外的 SubstanceMixer
枚举来方便处理环境混合器。
- 物质:使用
Flask.mix
便于突变 - 反应性物质:类似于物质,但还会观察全局物质混合器。
考虑以下示例
状态
struct AppState : State {
enum prop : StateProp{
case counter
}
var counter = 0
}
物质
class Feed : Substance<AppState> {}
反应性物质
enum Mixers : SubstanceMixer {
case Login
}
class App : ReactiveSubstance<AppState,Mixers> {
override func defineMixers(){
define(mix: . Login) { (payload, react, abort) in
self.prop.counter = self.prop.counter + 1
react()
}
}
}
混合器
Flask 数据突变使用混合器执行。混合器是事务闭包,其中您可以访问和修改给定物质属性。有两种类型的混合器
- 物质混合器:这些是在枚举中声明的,作为
ReactiveSubstace
子类定义的一部分传递。内部,物质可以根据需要 transcriptionally 订阅在 State 事务上下文中响应内部数据突变的案例。 - Flask混合器 这些可以在任何地方通过访问与给定
Reactor
实例绑定的物质来使用。调用reactor.mix(substance)
允许你在可以修改物质属性的 State Transaction 上下文中声明一个包含对请求物质的弱指针的闭包。
因此,虽然 物质混合器 可以应用于具有相同观察者的所有物质,但 Flask混合器 旨在进行更具体的转换,并且通过使用 ChainReaction
优势在于可以修改多个物质。
考虑以下示例
全局物质混合器。高级API
Flask.substances(reactTo:EnvMixers.Login, payload:["username":"foo"])
Flask混合器。高级API
Flask.getReactor(attachedTo:self)
.mixing(self.substance!) { (substance) in
substance.prop.counter = 10
}.with(self.substance!) { (substance) in
substance.prop.text = "text"
}.andReact()
反应器
反应器 Reactor
实例(或简称 reactor)通过传递一个弱 owner
引用和一组 [物质]
来由 ReactorManager
工厂初始化。内部,框架负责处理实例被 owner
设置为 nil 的懒加载解绑和销毁。
每个 Reactor
必须定义一个处理闭包,用于接收更改回调。你可以在这里看到示例实现
let reactor = Flask.reactor(attachedTo:owner,mixing:substance)
reactor.handler={owner, reaction in
reaction.on(AppState.prop.counter, { (change) in
expectation.fulfill()
})
}
上述实现被巧妙地通过 FlaskReactor
协议包裹,这种情况下实现看起来像这样
class ViewController: UIViewController, FlaskReactor {
func flaskReactions(reaction: FlaskReaction) {
reaction.on(AppState.prop.counter) { (change) in
print("counter = \(substance.state.counter)")
}
override func viewDidLoad() {
Flask.attachReactor(to:self, mixing:[substance, Subs.appReactive])
}
}
Flux
在Flask中,Flux 是一个单向单次派遣的总线,它确保所有混合操作都将以原子方式进行。
所有这些都在幕后发生,你不需要与 Flux 交互,只需派遣 SubstanceMixer 事件即可。
但是,可以使用 FluxLock
暂停 Flux,并在主 Flus 暂停的过程中进行独家混合。更重要的是,你可以创建异步锁,在反应闭包中执行的操作稍后释放。
下面将介绍有关锁的更多内容。
Flux是新来的吗?
你可以在 Lin Clark 的这篇教学文章中了解有关Flux的更多信息。 Flux的卡通指南
以及官方文档: 来自Facebook的Flux
实现
以下是关于如何在Redux和Fluxor模式相关实现Flask Reactor的快速参考。
Redux样式
这是类似于ReSwift的基本实现摘要。
Substance.swift
struct AppState : State{
enum prop: StateProp{
case counter, text
}
var counter = 0
var text = ""
}
ViewController
class ViewController: UIViewController, FlaskReactor {
func flaskReactions(reaction: FlaskReaction) {
reaction.on(AppState.prop.counter) { (change) in
print("counter = \(substance.state.counter)")
}
reaction.on(AppState.prop.text) { (change) in
print("text = \(substance.state.text)")
}
}
override func viewDidLoad() {
Flask.attachReactor(to:self, mixing:[substance])
produceTestReaction()
}
func produceTestReaction(){
Flask.getReactor(attachedTo:self)
.mixing(self.substance) { (substance) in
substance.prop.counter = 10
}.with(self.substance) { (substance) in
substance.prop.text = "changed!"
}.andReact()
}
}
Fluxor样式
Fluxor模式需要更多的设置,但是对于共享物质来说非常方便。
清单
enum EnvMixers : SubstanceMixer {
case Login
case Logout
}
class Subs {
static let appReactive = AppReactiveSubstance()
}
Substance.swift
struct AppState : State {
enum prop : StateProp{
case counter, title
}
var counter = 0
var title = ""
}
class AppReactiveSubstance : ReactiveSubstance<AppState,EnvMixers> {
override func defineMixers(){
define(mix: .Login) { (payload, react, abort) in
self.prop.title = "signed"
react()
}
}
}
ViewController
class ViewController: UIViewController, FlaskReactor {
func flaskReactions(reaction: FlaskReaction) {
reaction.on(AppState.prop.title) { (change) in
print("global title = \(Subs.appReactive.state.title)")
}
}
override func viewDidLoad() {
Flask.attachReactor(to:self, mixing:[Subs.appReactive])
produceTestReaction()
}
func produceTestReaction(){
Flask.substances(reactTo:EnvMixers.Login, payload:["username":"foo"])
}
}
特别功能
链式反应
调用mix()
(又称mixing()
)将返回一个Flask ChainReaction
实例,该实例可以进一步链式调用直到解决。一个ChainReaction
具有以下方法
- mix(substance:)
- react()
- abort()
要继续链式调用,只需再次调用mix(或其任何别名)。你必须在调用react()
或abort()
(或其任何别名)之前解决事务(否则你的Flask将无法执行进一步的mix事务)。
使用高级API
Flask.getReactor(attachedTo:self)
.mixing(self.substanceA) { (substance) in
substance.prop.counter = 10
}.with(self.substanceB) { (substance) in
substance.prop.text = "text"
}.andReact()
使用低级API
reactorInstance
.mix(self.substanceA) { (substance) in
substance.prop.counter = 10
}.mix(self.substanceB) { (substance) in
substance.prop.text = "text"
}.react()
锁
需要时,您可以创建一个 FluxLock
。这将暂停执行任何混合操作,包括 ReactiveSubstances
或 ChainReactions
。您可以创建许多锁,但您必须负责释放它们,否则会变得过于活跃。
let lock = Flask.lock()
// perform operations while the flux is paused
lock.release()
使用锁进行异步混合
有时您需要执行一个特定的混合操作,这需要暂停所有其他混合操作,直到 FlaskReaction
解析完成。
使用 ReactiveSubstance
来执行这一点非常简单
- 只需创建一个
FluxLock
并传递您全局的 EnvMixer 名称。 - 像往常一样执行您的
ReactiveSubstance
混合 - 然后在
FluxReaction
实例中的FluxReactor
,您将在reaction.onLock?
收到对您的锁的指针,以便您可以释放它。
示例
在锁定流量上请求混合
Flask.lock(withMixer: EnvMixers.AsyncAction)
异步释放
reaction.on(AppState.prop.asyncResult) { (change) in
//pass the reaction to an async block
DispatchQueue.main.asyncAfter(deadline: .now() + 2, execute: {
//release when the operation is completed
reaction.onLock?.release()
});
}
嵌套结构
可以使用嵌套结构并观察它们的更改。关于结构体的限制是所有属性都必须符合Codable协议。
如果您需要支持将对象作为顶级属性或嵌套传递,请阅读有关嵌套字典的部分。
func testStruct(){
let expectation = self.expectation(description: "testStruct")
let expectation2 = self.expectation(description: "testStruct")
let expectation3 = self.expectation(description: "testStruct")
struct nestedTestStruct:Codable{
var foo = "bar"
var object = FlaskNSRef(NSObject())
}
struct testStruct:Codable{
var counter = 10
var nest = nestedTestStruct()
}
struct state : State{
var info = testStruct()
}
let NAME = "substanceTest\( NSDate().timeIntervalSince1970)"
let mySubstance = Flask.newSubstance(definedBy: state.self,named:NAME, archive:false)
mySubstance.shouldArchive = true
let owner:TestOwner = TestOwner()
let reactor = Flask.reactor(attachedTo:owner, mixing:mySubstance)
reactor.handler = { owner, reaction in
mySubstance.archiveNow()
reaction.on("info", { (change) in
expectation.fulfill()
})
reaction.on("info.counter", { (change) in
expectation2.fulfill()
})
reaction.on("info.nest.foo", { (change) in
expectation3.fulfill()
})
}
reactor.mix(mySubstance) { (substance) in
substance.prop.info.counter = 90
substance.prop.info.nest.foo = "mutated"
}.andReact()
wait(for: [expectation,expectation2,expectation3], timeout: 2)
let expectation4 = self.expectation(description: "must preserve after archive")
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
let archivedSubstance = Flask.newSubstance(definedBy: state.self,named:NAME,archive:true)
XCTAssert(archivedSubstance.state.info.nest.foo == "mutated", "Must preserve value")
expectation4.fulfill()
}
wait(for: [expectation4], timeout: 4)
}
NSObjects 和字典
对象和归档:请注意,NSObjects 不可序列化,并由 SubstanceSerializer
映射为 nil
。
如果您需要监视 NSObject 的更改,可以使用 FlaskRef
。这将对您的对象进行包装,使其成为支持 Codable 协议的类,该协议是您 State 结构所必需的。
当监视这些键时,您将收到有关指向此对象的指针更改的通知。这将允许您监视像 UIViewController 这样的 UI 对象的更改。
struct myState : State{
var object = FlaskRef( NSObject() )
}
如果需要处理嵌套对象的层次结构,请使用 FlaskDictRef
。使用 NSDictionary 初始化这个类的实例,然后您将能够监视嵌套键的更改。
- 在您的状态中创建一个
FlaskDictRef
属性 - 通过将您的字典包装在
FlaskDictRef(Dictionary)
中来分配新值 - 使用点语法观察您的嵌套键的更改。
示例
创建一个 FlaskDictRef 属性
struct AppState : State {
enum prop : StateProp{
case info
}
var info:FlaskDictRef?
}
分配值
reactor.mix(substance){ (substance) in
let data:NSDictionary = [
"foo":"bar",
"nest":[
"data":"some"
]
]
substance.prop.info = FlaskDictRef(data)
}.react()
观察更改
reaction.on("info.nest.data", { (change) in
print(change.newValue()!)
})
归档
归档是在您不需要在数据中执行查询或关系操作时,相对于SQL-Lite或CoreData的绝佳替代方案。请注意,此功能依赖于UserDefaults作为存储目标。
默认情况下,归档是关闭的。要启用归档,只需将两个额外参数传递给Substance
或ReactiveSubstance
初始化器即可。
let substance = MySubstanceClass(name:"uniqueName",archive:true)
名称必须唯一,所以请确保为您的应用程序使用适当的名字约定。
检测到更改后,物质在2秒后将被归档。通过使用属性Substance.shouldArchive
,您可以在实例化后禁用归档。
您可以通过覆盖以下方法在你的Substance子类中进一步自定义此过程:
override func archiveKeySpace()->String{
return "1"
}
override func archiveKey()->String{
return "Fx.\(archiveKeySpace()).\(name())"
}
override func archiveDelay()->Double{
return 2.0
}
override func archiveDisabled()->Bool{
return !shouldArchive
}
内部状态属性
如果您想忽略某些State属性用于更改减少,只需在变量名前使用_
前缀即可。
struct AppState : State {
var _internal = "`_` use this prefix for internal vars "
}
如果您在状态中执行附加计算,这可能很有用。
底层API
在幕后,大多数高级功能依赖于在主Flask
类上调用状态方法。
您都可以在这里找到
purgeFluxQueue()
purgeFlasks()
instance(attachedTo:mixing:)
instance(attachedTo:mixing:)
lock()
lock(withMixer:)
lock(withMixer:payload:)
removeLocks()
applyMixer(_:payload:)
attachReactor(to:mixing:)
detachReactor(from:)
您还可以访问包含所有附加Reactor
实例的ReactorManager
。
reactors
purge()
文档
示例项目
本仓库中包含一个示例项目,位于
- FlaskSample/
请确保运行 Pod install
以创建工作区。
该示例应用程序同时实现两种模式,以供进一步参考。
测试用例
上述示例项目还附带数十个标准 Xcode 测试单元测试用例。这是一个学习更多实现模式的好来源。
这些测试在每个部署时都会与 Travis-CI 自动运行,您可以通过上方查看健康状况以确保顺利。
代码片段
我们正在准备更多实用示例,并乐于展示您的作品!
API 文档
请注意,文档仍在制作中。
在此处提供了 Jazzy 生成的文档:[文档](https://eonfluxor.github.io/reactor/)
有问题吗?
如果您需要任何帮助,请访问我们的 GitHub 问题。如果您无法从档案中找到任何解决方案,请随时提交问题。
您也可以通过以下方式联系我们
关于
Flask 由 Hassan Uriostegui 创建。