这是一个由 Swift 编写的极其简单的 "绑定框架"。它并没有什么特别之处...它只是用来调用监听函数的荣耀对象包装器。
这打算是一个独立的库,但要查看我在其上构建的配套 UI 内容,请查看 https://github.com/zacharyclaysmith/SwiftBindingUI.
如果您想要某个拥有更多星级和代码的内容,请查看 https://github.com/SwiftBond/Bond。它与这个项目惊人地相似(无意中),但它看起来有更多酷炫的添加和更稳健的文档。我不喜欢将视图和绑定代码耦合在单个项目中,但随便吧。
待办事项:更新本文件中示例,以反映 Swift 1.2 的某些更改。
github "zacharyclaysmith/SwiftBinding"
添加到您的 Cartfile 中carthage update
pod 'SwiftBinding'
公共变量 | 类型 | 描述 | 说明 |
---|---|---|---|
value | T | 设置此值将触发任何监听函数。 | |
v | T | value 的简短包裹器 |
公共方法 | 返回值 | 描述 | 说明 |
---|---|---|---|
addChangeListener(owner:NSObject, alertNow:Bool = false, listener:(ValueType) -> Void) | Void | 向此对象添加一个 "更改监听器",其中 owner 参数作为键。每当此对象的 value 发生更改或通过手动调用 alertChangeListeners() 时,都会通知这些监听器。 | 在未来版本中,owner 参数可能将被更通用的键系统所取代,因为 NSObjects 有点愚蠢。 |
removeChangeListener(owner:NSObject) | Void | 根据 owner 参数删除任何现有的更改监听器。 | |
alertChangeListeners() | Void | 通过 addChangeListener 方法添加的监听器的手动调用方式。 | 当您需要以比更改其核心值更微妙的方式更改值时(例如,当子属性更改时),非常有用。此外,在此阶段,此方法不调用 "anyUpdate" 监听器。那可能是一个错误。 |
addAnyUpdateListener(owner:NSObject, listener:() -> Void, alertNow:Bool) | Void | 向此对象添加一个 "任何更新" 监听器。 | 这是 addChangeListener 的冗余,但需要匹配 PUpdateListener 协议。 |
removeAnyUpdateListener(Owner:NSObject) | Void | 移除一个“任何更新”监听器。 | |
alertAnyUpdateListeners() | Void | 手动调用“任何更新”监听器的方法。 | 当您需要以比仅仅更改其核心值更为微妙的方式更改一个值时很有用(例如,当子属性更改时)。 |
BindableValue<T>
是这个可绑定库的核心。BindableValue<T>
非常简单...它有一个value
(值),changeListeners
(变更监听器),anyUpdateListeners
(任何更新监听器)。
value
属性值属性是BindableValue库的核心。其底层实现相当简单...只是一个设置器和获取器,其中设置器会调用一些监听器函数。
还有一个简称v
属性,可用于访问值属性。
每当调用value
设置器时,就会调用变更监听器(即在更改值属性时)。使用addChangeListener
方法添加一个变更监听器(听起来很显然,但现在您知道这是显然的)。
当您希望在绑定时立即调用监听器时,使用addChangeListener
的alertNow
参数。
调用removeChangeListener
进行清理...通常在 deinited 方法中。这对于与其监听器生命周期相关的值来说这不是特别重要,但对于长寿的值来说,不清理可能会严重影响性能和稳定性。
更新监听器是变更监听器的协议友好版本。这通常用于您需要将绑定到某种通用目的时,例如检测异构的PUpdateable对象集合的修改。使用addAnyUpdateListener
方法类似于addChangeListener
。
func example_addChangeListener(){
let someBindableString = BindableValue<String>(initialValue:"test")
someBindableString.addListener(self, listener: someListener)
someBindableString.value = "newValue" //EFFECT: someListener is called with a value of "newValue"
}
func someListener(value:String){
//DO SOMETHING
}
func example_alertNow(){
let someBindableString = BindableValue<String>(initialValue:"test")
someBindableString.addListener(self, listener: someListener, alertNow:true) //EFFECT: someListener is called with a value of "test"
someBindableString.value = "newValue" //EFFECT: someListener is called with a value of "newValue"
}
func someListener(value:String){
//DO SOMETHING
}
func example_removeChangeListener(){
let someBindableString = BindableValue<String>(initialValue:"test")
someBindableString.addChangeListener(self, listener: someListener, alertNow:true) //EFFECT: someListener is called with a value of "test"
someBindableString.value = "newValue" //EFFECT: someListener is called with a value of "newValue"
someBindableString.removeChangeListener(self)
someBindableString.value = "anotherValue" //EFFECT: someListener is NOT called.
}
func someListener(value:String){
//DO SOMETHING
}
CalculatedValue<T>
CalculatedValue<T>
是此框架真正强大之处开始显现的地方。这些基本上是BindableValue<T>
,它们会积极监听其他BindableValue<T>
,并通过calculator
属性进行计算,从而产生一个新的值。《CalculatedValue<T>可以监听任何数量的PUpdateable
协议实现类(有趣的一旁注解,这就是创建该协议的原因)。
为了易于互操作性(以及Swift协议和泛型的法律可能相当令人沮丧,CalculatedValue<T>
只是BindableValue<T>
的子类型。这意味着,尽管value
和v
属性设置器是公开的,但不应使用它们,或预期会有严重后果(或者至少是不期望的行为...随便吧,您已经被警告了)。
calculator
这仅仅是一个生成特定T
值的函数,供CalculatedValue<T>
对象使用。此方法不接收任何参数...被绑定值应从更大的范围内引用。如果您不明白这些,请参阅示例。
func example_basicCalculatedValueUsage(){
let bindableInteger = BindableValue<Int>(value: 0)
let calculatedCurrencyString = CalculatedValue<String>([bindableInteger], calculator:{() -> String in
return "$" + toString(bindableInteger.value) + ".00"
})
//EFFECT: calculatedCurrencyString.v == "$0.00"
bindableInteger.value = 15
//EFFECT: calculatedCurrencyString.v == "$15.00"
}
请记住,您可以从其他CalculatedValue
创建新的CalculatedValue
(只是小心不要递归地将值绑定到自身...否则会形成黑洞)。
BindableValue<T>
-> CalculatedValue<T>
扩展为了让代码更具语法和语义上的吸引力,我编写了以下扩展方法,这些方法可以帮助清理一些常见用例。这些方法包括 transform
和 combine
。
transform
允许您轻松地将一个 BindableValue
转换为另一个。
func example_transform(){
let bindableInteger = BindableValue<Int>(value: 0)
let calculatedCurrencyString = bindableInteger.transform({"$" + toString(bindableInteger.value) + ".00"}) //EFFECT: calculatedCurrencyString.v == "$0.00"
bindableInteger.value = 15 //EFFECT: calculatedCurrencyString.v == "$15.00"
}
func example_combineSingle(){
let bindableInteger = BindableValue<Int>(value: 1)
let bindableInteger2 = BindableValue<Float>(value: 5.0)
let sumCurrencyString = bindableInteger.combine(bindableInteger2, calculator: "$" + toString((bindableInteger.value + bindableFloat.value)) + ".00") //EFFECT: calculatedCurrencyString.v == "$6.00"
bindableInteger.value = 15 //EFFECT: calculatedCurrencyString.v == "$20.00"
}
func example_combineMultiple(){
let bindableInteger = BindableValue<Int>(value: 1)
let bindableInteger2 = BindableValue<Int>(value: 5.0)
let bindableInteger3 = BindableValue<Int>(value: 10.0)
let sumDollarString = bindableInteger.combine([bindableInteger2, bindableInteger3],
calculator: "$" + toString((bindableInteger.value + bindableFloat.value + bindableFloat2.value)) + ".00")
//EFFECT: calculatedCurrencyString.v == "$16.00"
bindableInteger.value = 15 //EFFECT: calculatedCurrencyString.v == "$31.00"
}
注意:虽然以下示例为了简洁都是整数(并且因为我是一个懒散的示例编写者),但请记住,您可以结合任何所有值类型到一个单个的计算值。
可绑定值很有趣,但可绑定量组是绑定框架制作奶酪(或者任何你想插入的成语)的地方。我已经尽量保持 BindableArray
简单;您可以选择监听数组,并在索引发生变化或数组结构发生变化时检测到。
BindableArray
是 Swift Array
类的包装器,这意味着 BindableArray
重新实现了 Swift Array
类中的许多方法和功能,并在其中插入了一些绑定触发器。例如,子数组访问被包装,以便 someStringArray[5] = "newValue"
触发索引更改监听器。这里仍然在进行包装,所以公开属性 internalArray
和公开 notifyIndexChangedListeners
和 notifyChangedListeners
函数被暴露出来,以便可以进行手动更改和触发。
一般来说,每当单个索引受到操作的影响时,都会调用它。此类操作包括:索引赋值/修改和 append
。
注意:对于大量更新的数组值,建议修改 internalArray
属性上的值,然后在最后调用 notifyChangedListeners
来节省执行周期。
更改监听器在整体数组发生更改时被调用,例如在删除、分割或大量更改时。
我在开发中倾向于采用“坏了就优化”的方法,因此我没有在数组的操作上花太多时间进行微调,因为它们还没有给我带来任何麻烦。如果您需要在 BindableArray
或 CalculatedArray
做得更好,或者如果您想自己贡献:)的话,请随时联系我。
func example_append(){
let bindableArray = BindableArray<String>(initialArray:[])
bindableArray.addIndexChangedListener(self, listener:someListener)
bindableArray.append("someString") //EFFECT: someListener is called with index == bindableArray.length - 1
}
func someListener(index:Int){
//DO SOMETHING
}
CalculatedArray
待办事项...实际上非常棒,但我在一天之内只能写出这么多文档。
我想为 iOS/Swift 创建一个简单的绑定框架,所以我开始了这个项目。这已经证明非常有用。我多年来使用过几个绑定框架,并且我已经了解到基本的价值和数组绑定支持确实是您所需要的,再加上一些 UI 糖分(参看 https://github.com/zacharyclaysmith/SwiftBindingUI 了解更多)。
我不认为这个库已经完成或经过良好测试,但它是有功能的,是一个良好的起点。然而,我希望尽可能地保持简单,并在其周围构建其他库。
您的支持。还有我对在iOS中管理UI数据的绝对厌恶。
MIT