最近一段时间在看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源码简析:包括线程隔离和信号量隔离实现分析、熔断器实现分析等
《网狐6.6完整源码与内核源码解析:105款游戏源码解密探索》 在IT行业中,源码是程序开发的核心,它揭示了软件的内部工作机制,是程序员进行二次开发、优化和调试的基础。"网狐6.6完整源码+内核源码+105款游戏源码...
SSCOM源码 DELPHI 源码 绝对源码!欢迎下载
电商微信小程序源码+后台分享,亲测可用,有需要的朋友拿去!!! 电商微信小程序源码+后台分享,亲测可用,有需要的朋友拿去!!! 电商微信小程序源码+后台分享,亲测可用,有需要的朋友拿去!!! 电商微信小程序...
订餐网,外卖网源码,带积分商城,商家系统,外卖网站建设! 系统特点: 周密策划、项目为先 "项目指导技术,技术服从项目",这是我们一贯秉承的原则,也是我们与其他系统开发商、网站建设公司的本质区别所在!我们...
易语言源码就是用这种语言编写的程序代码,通过阅读和理解这些源码,开发者可以学习到如何利用易语言来实现特定功能,比如变速齿轮。 在易语言中实现变速齿轮功能,主要涉及到以下几个关键知识点: 1. **系统时间...
小程序源码、小游戏源码下载地址: https://gitee.com/giteesource/app/blob/master/resource/1008.md 包含源码有: 1、游戏小程序:轻松愉快的休闲时光 2、商城小程序:便捷的在线购物体验 3、工具小程序:实用的...
移动医疗APP源码是开发医疗健康应用的核心组成部分,它包含了应用程序的所有逻辑和界面设计。在Android平台上,这种源码通常是用Java或Kotlin语言编写的,并使用Android Studio作为集成开发环境(IDE)。在这个案例...
《cocos creator完整麻将源码解析与开发指南》 cocos Creator是一款强大的2D游戏开发引擎,被广泛应用于游戏开发,尤其是休闲娱乐类游戏,如麻将。本篇将深入探讨"麻将源码"这一主题,结合cocos Creator的特性,为...
0001-2科技发展有限公司升级版源码 0001科技发展有限公司修正版源码 0002机械配件制造销售公司修正版源码 0003家具地板公司修正版源码 0004-1机械有限公司修正版源码 0004机械有限公司修正版源码 0005机械产品公司...
源码01 销售管理系统 源码02 彩票分析系统 源码03 餐饮管理系统 源码04 C#点名程序 源码05 象棋游戏 源码06 变色球游戏 源码07 多功能计算器 源码08 记事本 源码09 简易画图程序 源码10 成绩管理系统 源码11 BBS论坛...
【DIY个性T恤定制网站源码】是一个用于创建在线个性化商品定制平台的软件系统,主要专注于T恤、杯子、台历和挂历等产品。这个源码允许用户通过简单的界面设计自己的产品,体现个人风格和创意。接下来,我们将深入...
源码完全由开发者手工编写,具备全后台动态更新前台的功能,这意味着管理员可以通过后台管理系统方便地更新和管理网站内容,无需懂得前端编程。 源码的亮点在于其灵活性和实用性,允许用户快速部署并开始运营,尤其...
Linux系统下的DHCP(Dynamic Host Configuration Protocol)源码解析 DHCP是一种网络协议,用于自动分配IP地址、子网掩码、默认网关等网络配置信息给网络中的设备。在Linux环境中,DHCP服务器通常使用isc-dhcp-...
【标题】:“多功能在线报价系统源码” 在线报价系统是一种基于网络的应用程序,它使得企业或个人能够方便快捷地提供产品或服务的价格信息给潜在客户。这个“多功能在线报价系统源码”是专为此目的设计的,允许用户...
【产品展示网站源码】是一种用于在线展示公司产品的软件代码,它构成了一个网站的基础结构,允许企业以有吸引力的方式向访客展示其商品或服务。此类源码通常包含前端和后端组件,前者负责用户界面的设计和交互,后者...
标题中的“非常漂亮的个人博客网站源码”表明这是一个关于个人博客网站的设计与开发资源,它包含了一套完整的源代码,可以用于创建一个美观且个性化的博客平台。这种源码通常包括HTML、CSS、JavaScript等前端代码,...
在源码层面,53客服系统源码主要包含了以下几个关键知识点: 1. **多平台支持**:53客服系统通常支持网页、手机APP、微信等多种渠道的接入,源码中会有相应的接口实现,以确保用户可以通过不同的终端与客服进行交互...
【欢乐斗地主的详细源码】是一款基于经典扑克游戏斗地主的程序实现,它提供了游戏的核心逻辑、用户交互界面以及可能的游戏算法。在这个源码中,我们可以深入理解游戏的开发过程,学习如何构建一个功能完备的多人在线...