最近一段时间在看swift的数据绑定,所以找到了开源库swiftbond
这是个很巧妙的设计,充分使用了swift的语言特性。
关于这个库的实现过程,上一篇blog也有讲,不过显然那只是个原理,并不是最终的结果。
我们看看看看这个库的核心类Dynamic<T>,这个类是bond库的灵魂,这是个模板类,用于各种类型的数据变量。正是这个类使得变量在变化的同时触发一系列事件,达到绑定的目的。
// MARK: Dynamic
public class Dynamic<T> {
private var dispatchInProgress: Bool = false
internal var _value: T? {
didSet {
objc_sync_enter(self)
if let value = _value {
if !self.dispatchInProgress {
dispatch(value)
}
}
objc_sync_exit(self)
}
}
public var value: T {
set {
_value = newValue
}
get {
if _value == nil {
fatalError("Dynamic has no value defined at the moment!")
} else {
return _value!
}
}
}
public var valid: Bool {
get {
return _value != nil
}
}
private func dispatch(value: T) {
// clear weak bonds
self.bonds = self.bonds.filter {
bondBox in bondBox.bond != nil
}
// lock
self.dispatchInProgress = true
// dispatch change notifications
for bondBox in self.bonds {
bondBox.bond?.listener?(value)
}
// unlock
self.dispatchInProgress = false
}
public let valueBond = Bond<T>()
public var bonds: [BondBox<T>] = []
private init() {
_value = nil
valueBond.listener = { [unowned self] v in self.value = v }
}
public init(_ v: T) {
_value = v
valueBond.listener = { [unowned self] v in self.value = v }
}
public func bindTo(bond: Bond<T>) {
bond.bind(self, fire: true, strongly: true)
}
public func bindTo(bond: Bond<T>, fire: Bool) {
bond.bind(self, fire: fire, strongly: true)
}
public func bindTo(bond: Bond<T>, fire: Bool, strongly: Bool) {
bond.bind(self, fire: fire, strongly: strongly)
}
}
剥离一些细节,我们看看这个类的两个灵魂变量,那就是valueBond,bonds。这两个变量最终放置的都是类Bond的实例,至于Bond我们暂时先不讲,它的灵魂变量是个Listener,所以你可以先把Bond看成一个执行block。
- valueBond 是用来改变Dynamic自身变量的block。通过Bond类的源码可以知道,这是个强引用。
- bonds 用来指向别的Dynamic的valueBond,当自身变量发生变化时就会执行这些bonds,这是个弱引用
这样我们就形成了一个绑定链,比如DynamicA.bonds->DynamicB.valueBond, DynamicB.bonds->DynamicC.valueBond
这样当DynamicA的值发生变化是,就会触发DynamicB,DynamicC的变化。
对于ios开发,数据绑定,通常最终会反映到一个具体的控件,比如UILable, UITextField, UIButton等等。
那么swiftbond是通过给每个控件添加一个Dynamic类型的属性来实现绑定的。
比如UILable的实现
import UIKit private var textDynamicHandleUILabel: UInt8 = 0; private var attributedTextDynamicHandleUILabel: UInt8 = 0; extension UILabel: Bondable { public var dynText: Dynamic<String> { if let d: AnyObject = objc_getAssociatedObject(self, &textDynamicHandleUILabel) { return (d as? Dynamic<String>)! } else { let d = InternalDynamic<String>(self.text ?? "") let bond = Bond<String>() { [weak self] v in if let s = self { s.text = v } } d.bindTo(bond, fire: false, strongly: false) d.retain(bond) objc_setAssociatedObject(self, &textDynamicHandleUILabel, d, objc_AssociationPolicy(OBJC_ASSOCIATION_RETAIN_NONATOMIC)) return d } } public var dynAttributedText: Dynamic<NSAttributedString> { if let d: AnyObject = objc_getAssociatedObject(self, &attributedTextDynamicHandleUILabel) { return (d as? Dynamic<NSAttributedString>)! } else { let d = InternalDynamic<NSAttributedString>(self.attributedText ?? NSAttributedString(string: "")) let bond = Bond<NSAttributedString>() { [weak self] v in if let s = self { s.attributedText = v } } d.bindTo(bond, fire: false, strongly: false) d.retain(bond) objc_setAssociatedObject(self, &attributedTextDynamicHandleUILabel, d, objc_AssociationPolicy(OBJC_ASSOCIATION_RETAIN_NONATOMIC)) return d } } public var designatedBond: Bond<String> { return self.dynText.valueBond } }
关于方法objc_getAssociatedObject,objc_setAssociatedObject的使用,请自行查阅资料。
可以看出这里面添加了两个Dynamic类型的属性dynText,dynAttributedText。
当一个viewModel的变量绑定到一个UILable的时候,其实是绑定到这个dynText上了,也就是说UILable的text属性值是经由dynText来改变的,所以dynText的bonds里一定要保存能改变UILable.text的Bond(也就是Listener),这是由上面蓝色代码实现的。
通常如果是一个Dynamic类型的变量的话,它自身会strong reference能改变自身值得valueBond,但是如果这个绑定链的终端是个非Dynamic类型的值的话,那自然需要新创建一个Bond,但是这个Bond并没有被任何一个变量强引用,会丢失的,对于控件,是让新添加的Dynamic类型变量来retain这个Bond的。
当然如果是在controller里创建的Bond的话,应该让controller或者它retain的变量去retian这个Bond了。
既然是个绑定链,自然中间会产生多个Dynamic类型的变量打个比方
UILable.dynText<-DynamicA<-DynamicB<-viewModel.dynText
对于UILable.dynText和viewModel.dynText,通常都会由controller直接或者间接强引用,所以不用担心会被释放掉。
但是对于中间产生的DynamicA,DynamicB并没有被哪个类强引用着,那么是怎么解决的呢?
最终,我们还得来看看类Bond
public class Bond<T> { public typealias Listener = T -> Void public var listener: Listener? public var bondedDynamics: [Dynamic<T>] = [] public var bondedWeakDynamics: [DynamicBox<T>] = [] public init() { } public init(_ listener: Listener) { self.listener = listener } public func bind(dynamic: Dynamic<T>) { bind(dynamic, fire: true, strongly: true) } public func bind(dynamic: Dynamic<T>, fire: Bool) { bind(dynamic, fire: fire, strongly: true) } public func bind(dynamic: Dynamic<T>, fire: Bool, strongly: Bool) { dynamic.bonds.append(BondBox(self)) if strongly { self.bondedDynamics.append(dynamic) } else { self.bondedWeakDynamics.append(DynamicBox(dynamic)) } if fire && dynamic.valid { self.listener?(dynamic.value) } } public func unbindAll() { let dynamics = bondedDynamics + bondedWeakDynamics.reduce([Dynamic<T>]()) { memo, value in if let dynamic = value.dynamic { return memo + [dynamic] } else { return memo } } for dynamic in dynamics { var bondsToKeep: [BondBox<T>] = [] for bondBox in dynamic.bonds { if let bond = bondBox.bond { if bond !== self { bondsToKeep.append(bondBox) } } } dynamic.bonds = bondsToKeep } self.bondedDynamics.removeAll(keepCapacity: true) self.bondedWeakDynamics.removeAll(keepCapacity: true) } }
我们可以看到有两个数组bondedDynamics,bondedWeakDynamics,
没错,就是由Bond来强引用的,对于上面举的那个例子。
结果就是
UILable.dynText.valueBond强引用DynamicA
DynamicA.valueBond强引用DynamicB
DynamicB.valueBond强引用viewModel.dynText
大家可能会注意到viewModel.dynText会被controller和DynamicB.valueBond两个实例强引用。
对于这个问题,我也问过作者,结论就是为了统一实现方法,并且因为没有形成引用环,只要controller释放的话,就会都释放掉的,所以没有问题。但是这个地方在实装的时候确实值得小心。
对于类Bond里的另外一个属性bondedWeakDynamics,这个是为了解决双向绑定设置的。
还是上面的例子,如果是双向绑定的话,反向引用应该是这样的关系
viewModel.dynText.valueBond弱引用DynamicB
DynamicB.valueBond弱引用DynamicA
DynamicA.valueBond弱引用UILable.dynText
当然这儿的引用到不是(也没办法)解决中间Dynamic丢失的问题。
而是为了实现Bond.unbindAll方法而保存Dynamic的。
上面说到双向绑定,那么是怎么阻止循环更新的?这就用到了Dynamic类里的dispatchInProgress变量,它在触发执行绑定的bonds时,会判断以及更新这个变量,会阻断循环更新的执行。
另外,对Dynamic的value赋值后,所有的Bond都是无条件触发的,不管这个value赋值前后是否一样。所以尽量不要在这个绑定链里放一些特别复杂的处理。
大概就是这个样子。
相关推荐
Hystrix源码简析:包括线程隔离和信号量隔离实现分析、熔断器实现分析等
在对Resiprocate源码进行分析时,我们需要理解其核心架构和设计模式。源码分析通常包括以下几个方面: 1. **模块划分**:Resiprocate的源码可能分为多个模块,每个模块负责特定的功能,如网络传输、SIP消息处理、...
这里我们将深入探讨XML在Android中的应用、解析机制以及源码分析。 首先,XML在Android系统中的作用至关重要。布局文件(如activity_main.xml)使用XML定义UI组件,包括按钮、文本框、图像视图等。通过XML,开发者...
SSCOM源码 DELPHI 源码 绝对源码!欢迎下载
本资源包含2000套微信小程序的源码,对于开发者来说是一份宝贵的参考资料,可以用来学习、研究或者作为开发新项目的起点。 源码下载是开发者获取程序原始代码的方式,对于学习和理解编程逻辑至关重要。这些微信小...
电商微信小程序源码+后台分享,亲测可用,有需要的朋友拿去!!! 电商微信小程序源码+后台分享,亲测可用,有需要的朋友拿去!!! 电商微信小程序源码+后台分享,亲测可用,有需要的朋友拿去!!! 电商微信小程序...
【借贷公司源码 网贷平台源码 php借贷源码】这个标题揭示了我们要讨论的核心内容,即一套用于建立在线借贷或网贷平台的源代码,该源代码是基于PHP编程语言实现的。PHP是一种广泛使用的开源服务器端脚本语言,尤其在...
订餐网,外卖网源码,带积分商城,商家系统,外卖网站建设! 系统特点: 周密策划、项目为先 "项目指导技术,技术服从项目",这是我们一贯秉承的原则,也是我们与其他系统开发商、网站建设公司的本质区别所在!我们...
易语言源码就是用这种语言编写的程序代码,通过阅读和理解这些源码,开发者可以学习到如何利用易语言来实现特定功能,比如变速齿轮。 在易语言中实现变速齿轮功能,主要涉及到以下几个关键知识点: 1. **系统时间...
《cocos creator完整麻将源码解析与开发指南》 cocos Creator是一款强大的2D游戏开发引擎,被广泛应用于游戏开发,尤其是休闲娱乐类游戏,如麻将。本篇将深入探讨"麻将源码"这一主题,结合cocos Creator的特性,为...
ssh框架项目源码ssh框架项目源码ssh框架项目源码ssh框架项目源码ssh框架项目源码ssh框架项目源码ssh框架项目源码ssh框架项目源码ssh框架项目源码ssh框架项目源码ssh框架项目源码ssh框架项目源码ssh框架项目源码ssh...
移动医疗APP源码是开发医疗健康应用的核心组成部分,它包含了应用程序的所有逻辑和界面设计。在Android平台上,这种源码通常是用Java或Kotlin语言编写的,并使用Android Studio作为集成开发环境(IDE)。在这个案例...
源码01 销售管理系统 源码02 彩票分析系统 源码03 餐饮管理系统 源码04 C#点名程序 源码05 象棋游戏 源码06 变色球游戏 源码07 多功能计算器 源码08 记事本 源码09 简易画图程序 源码10 成绩管理系统 源码11 BBS论坛...
0001-2科技发展有限公司升级版源码 0001科技发展有限公司修正版源码 0002机械配件制造销售公司修正版源码 0003家具地板公司修正版源码 0004-1机械有限公司修正版源码 0004机械有限公司修正版源码 0005机械产品公司...
Linux系统下的DHCP(Dynamic Host Configuration Protocol)源码解析 DHCP是一种网络协议,用于自动分配IP地址、子网掩码、默认网关等网络配置信息给网络中的设备。在Linux环境中,DHCP服务器通常使用isc-dhcp-...
【标题】:“多功能在线报价系统源码” 在线报价系统是一种基于网络的应用程序,它使得企业或个人能够方便快捷地提供产品或服务的价格信息给潜在客户。这个“多功能在线报价系统源码”是专为此目的设计的,允许用户...
本话题聚焦于一个特殊的软件组件——“机顶盒聚合视频桌面源码”,这是一种为机顶盒定制的用户界面(UI)和应用,它整合了多个视频来源,如直播频道和点播服务。 聚合视频桌面源码是开发机顶盒应用的核心部分,它的...
【产品展示网站源码】是一种用于在线展示公司产品的软件代码,它构成了一个网站的基础结构,允许企业以有吸引力的方式向访客展示其商品或服务。此类源码通常包含前端和后端组件,前者负责用户界面的设计和交互,后者...
标题中的“非常漂亮的个人博客网站源码”表明这是一个关于个人博客网站的设计与开发资源,它包含了一套完整的源代码,可以用于创建一个美观且个性化的博客平台。这种源码通常包括HTML、CSS、JavaScript等前端代码,...