SimpleStates
Swift 中用于响应式编程的简单状态
允许您将 “状态” 附带到 Swift 中给定的属性,并且当状态改变时,该属性也会改变。
与其他库(如 ReactiveSwift)相比,它更加轻量级,且更容易理解(尽管如果您需要进行任何重量级操作,您可能需要使用那些库)。
安装
SimpleStates 可以通过 CocoaPods 获取。要安装它,请简单地添加以下行到您的 Podfile 中
pod 'SimpleStates'
使用方法
基本用法
import SimpleStates
let myState = State("hello")
let label = UILabel(CGRect(x: 0, y: 0, width: 100, height: 20))
var changingString = "initial"
myState.bind(label, .\text)
// label.text is now "hello"
myState.on({ newValue in
changingString = newValue
})
// changingString is now "hello"
myState.set("hello world")
// label.text and changingString are both now "hello world"
状态类型
提供了 3 种状态类型
普通状态
保存一个值,当用户修改此值时更新观察者。
构造方式如下:
let myState = State(defaultValue)
或者,如果您不想依赖编译器的推断,可以显式设置状态的类型。
let myState = State<CGFloat>(1.0) // would normally be Float
通知中心状态
当发出通知时更新观察者。
这些可以通过两种方式构造。
第一种方式需要三个参数:要监听的通知、状态的初始值,以及一个闭包,该闭包从通知中转换Notification
参数并将返回的值设置为状态的值。
let isPortrait = NotificationState(
UIDevice.orientationDidChangeNotification,
default: "",
mutator: { notif in
return notif.description
}
)
如果您不需要通知参数的值,可以通过只传递要监听的通知和一个属性获取器来构造状态。
let isPortrait = NotificationState(
UIDevice.orientationDidChangeNotification,
getter: UIDevice.current.orientation
)
这将更新状态的值,在接收到通知时始终为UIDevice.current.orientation。
键值观察状态
观察给定遵守KVO协议的属性,并在它更改时更新状态。
这是使用对象和一个属性KeyPath初始化的,如下所示
let myState = KVOState(obj: myObject, keyPath: \.propertyName)
通常,KVO状态的属性值是不可变的,以保持与所跟踪属性的连贯性。如果跟踪的属性是可变的,并且您想更改它,则可以使用MutableKVOState
,然后使用.setValue来设置支持变量的值。
let myState = MutableKVOState(obj: myObject, keyPath: \.propertyName)
通常,您可以使用myObject.propertyName = newValue来实现相同的效果,但这将允许您更新状态,即使您没有这些引用。
注意
为了避免内存泄漏,KVOState不会保留其绑定对象的引用,因此您必须确保您自己不要释放它。
绑定状态
可以绑定一个状态的类属性,使它在更新时更新该属性。
这可以通过几种方式完成:
let label = UILabel()
/// with a KeyPath
myState.bind(label, .\text)
// or with a BundledKeyPath (more on that later)
myState(label..\.text)
// or with some nice helper functions
label..\.text <-> myState
/// These all do the exact same thing
第一个选项是一个函数,它接受两个参数:一个对象和一个指向您想要绑定的属性的KeyPath
。很简单。
第二个选项与第一个相似,但是在State对象本身上调用的。这里我使用的是BundleKeyPath,它包含我们指向的对象和路径本身,但这两个都接受两种类型的参数。
最后一个选项是几个辅助函数的组合。
..
:它接受一个对象和一个KeyPath,将它们组合成一个名为BundledKeyPath
的单个对象,然后可以将其绑定到状态。如果您对象中不存在指定的KeyPath,它将抛出一个错误。
<->
:它将给定的BundledKeyPath
绑定到State
。
设置绑定时,属性会立即更新为状态的值,并且每当状态的值更新时都会更新。
更新状态
要更新vanilla状态的值,请使用State.set
方法
myState.set("New Text")
label.text
已经更新为我们新的值(并且UIKit将自动更新)
注意
KVOState和NotificationState会自动更新,无法手动设置
获取状态的当前值
要获取当前值而不附加监听器,只需调用State.get()
let myState = State("Text")
myState.get()
/// returns "Text"
高级状态绑定
有时,简单的属性绑定就不够了。例如,如果您需要首先修改值,或者如果您需要调用单独的更新函数,比如使用UITableView
。
在这种情况下,您不使用State.bind(BundledKeyPath)
或State.bind(Any, KeyPath)
,而是使用State.on((newValue)->Void)
,这允许您传递一个自定义闭包,在值更新时调用。
此示例将在更新之前将笑脸添加到标签文本的前面
let myState = State("Text")
let label = UILabel()
myState.on({newValue in
label.text = val + " 😀"
})
myState.set("hello world")
/// label.text is now "hello world 😀"
对于表格视图之类的用法,例如,您可以在闭包内通知视图从内部重新加载,然后在实际的渲染方法中调用 .get()。每当您调用 dataSource.set 时,tableView 都将重新渲染。
let dataSource = State<[String]>([])
override func viewDidLoad() {
super.viewDidLoad()
dataSource.on({ _ in
self.tableView.reloadData()
})
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier", for: indexPath)
cell.titleLabel.text = dataSource.get()[indexPath.row]
// Configure the cell...
return cell
}
当然,您可以在普通属性上使用 didSet
来做这个特定的操作,但这是一个例子
注意
State.on
闭包总是在主队列上调用,所以您无需担心在它们内部进行UI更新
从状态中解绑属性
每次创建绑定时,都会返回一个 Binding
,允许您稍后使用 State.unBind
解绑
let myState = State("Text")
let label = UILabel()
let labelBinding = myState.bind(label, \.text)
/// OR
let labelBinding = label..\.text <-> myState
myState.set("Hello World")
/// label.text is now "Hello World"
myState.unBind(labelBinding)
myState.set("Goodbye World")
/// label.text is still "Hello World"
其他方法
您可以直接使用默认绑定创建一个状态,但这将不允许您将来解绑它。
let myLabel = UILabel()
let myState = boundState(myLabel..\.text, "Text")
/// myLabel.text is now "Text"
myState.set("New Text")
/// myLabel.text is now "New Text"
您还可以使用类似于React的state hook的语法创建一个状态对象
let (myState, getMyState, setMyState) = useState("Text")
这返回一个包含状态对象、获取器函数(该函数不会添加监听器)和设置器函数的元组。
可以使用如下方式使用它
let (myState, getMyState, setMyState) = useState("Text")
let myLabel = UILabel()
myLabel..\.text <-> myState
setMyState("New Text")
/// myLabel.text is now "New Text"
getMyState()
/// returns "New Text"
捆绑键路径
为简化创建绑定时的操作,存在一个结构体 BundledKeyPath
,它专门用于存储一个键路径及其所在的对象的引用。
要创建一个这样的实例,您可以直接调用构造函数,或者使用 ..
函数。
let myLabel = UILabel()
let bkp = BundledKeyPath(myLabel, \.text)
// OR
let bkp = myLabel..\.text
这样做只是使使用给定中缀函数构建绑定变得更容易,因为我们需要对对象和键路径的引用来实际设置给定的属性,而Swift不允许我们创建自己的三元运算符。
当构建一个 MutableKVOState
时,您也可以使用这些功能。
let state = MutableKVOState(myClass..\.myKVOConformingProperty)
注解
捆绑键路径始终可变,因为不可变属性不能绑定到状态(显然的原因)。它们不能通过只读属性创建。
就是这样! 如果有任何问题,请告诉我!
作者
Bryce Dougherty – [email protected]
许可
本项目受MIT许可提供