`
weiqingfei
  • 浏览: 315378 次
  • 性别: Icon_minigender_1
  • 来自: 黑洞
社区版块
存档分类
最新评论

SwiftBond源码简析

    博客分类:
  • IOS
阅读更多

最近一段时间在看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源码简析

    Hystrix源码简析:包括线程隔离和信号量隔离实现分析、熔断器实现分析等

    Resiprocate源码简析

    在对Resiprocate源码进行分析时,我们需要理解其核心架构和设计模式。源码分析通常包括以下几个方面: 1. **模块划分**:Resiprocate的源码可能分为多个模块,每个模块负责特定的功能,如网络传输、SIP消息处理、...

    YY多开器源码源码YY多开器源码源码

    YY多开源码YY多开器源码源码YY多开器源码源码YY多开器源码源码

    ssh框架项目源码ssh框架项目源码ssh框架项目源码

    ssh框架项目源码ssh框架项目源码ssh框架项目源码ssh框架项目源码ssh框架项目源码ssh框架项目源码ssh框架项目源码ssh框架项目源码ssh框架项目源码ssh框架项目源码ssh框架项目源码ssh框架项目源码ssh框架项目源码ssh...

    android 简析xml源码

    这里我们将深入探讨XML在Android中的应用、解析机制以及源码分析。 首先,XML在Android系统中的作用至关重要。布局文件(如activity_main.xml)使用XML定义UI组件,包括按钮、文本框、图像视图等。通过XML,开发者...

    SSCOM源码 DELPHI 源码

    SSCOM源码 DELPHI 源码 绝对源码!欢迎下载

    变速齿轮 易语言源码 变速齿轮源码 变速器源码

    易语言源码就是用这种语言编写的程序代码,通过阅读和理解这些源码,开发者可以学习到如何利用易语言来实现特定功能,比如变速齿轮。 在易语言中实现变速齿轮功能,主要涉及到以下几个关键知识点: 1. **系统时间...

    借贷公司源码 网贷平台源码 php借贷源码

    【借贷公司源码 网贷平台源码 php借贷源码】这个标题揭示了我们要讨论的核心内容,即一套用于建立在线借贷或网贷平台的源代码,该源代码是基于PHP编程语言实现的。PHP是一种广泛使用的开源服务器端脚本语言,尤其在...

    iptables源码iptables源码

    iptables源码 iptables工具

    cocos creator完整麻将源码下载

    《cocos creator完整麻将源码解析与开发指南》 cocos Creator是一款强大的2D游戏开发引擎,被广泛应用于游戏开发,尤其是休闲娱乐类游戏,如麻将。本篇将深入探讨"麻将源码"这一主题,结合cocos Creator的特性,为...

    移动医疗APP源码 android (安卓版)妙手医生源码

    移动医疗APP源码是开发医疗健康应用的核心组成部分,它包含了应用程序的所有逻辑和界面设计。在Android平台上,这种源码通常是用Java或Kotlin语言编写的,并使用Android Studio作为集成开发环境(IDE)。在这个案例...

    C#项目源码大集合系列一

    源码01 销售管理系统 源码02 彩票分析系统 源码03 餐饮管理系统 源码04 C#点名程序 源码05 象棋游戏 源码06 变色球游戏 源码07 多功能计算器 源码08 记事本 源码09 简易画图程序 源码10 成绩管理系统 源码11 BBS论坛...

    51套经典企业网站源码(一)

    0001-2科技发展有限公司升级版源码 0001科技发展有限公司修正版源码 0002机械配件制造销售公司修正版源码 0003家具地板公司修正版源码 0004-1机械有限公司修正版源码 0004机械有限公司修正版源码 0005机械产品公司...

    捕鱼游戏源码 下载 最新完整版

    捕鱼游戏源码是一种基于计算机编程技术,用于开发模拟海洋捕鱼场景的电子游戏的代码集合。这类源码通常包含了游戏逻辑、图形渲染、音频处理、用户交互等多个方面的详细实现,为开发者提供了一个深入理解游戏开发过程...

    DIY个性T恤定制网站源码

    【DIY个性T恤定制网站源码】是一个用于创建在线个性化商品定制平台的软件系统,主要专注于T恤、杯子、台历和挂历等产品。这个源码允许用户通过简单的界面设计自己的产品,体现个人风格和创意。接下来,我们将深入...

    53客服系统源码 53kf源码

    在源码层面,53客服系统源码主要包含了以下几个关键知识点: 1. **多平台支持**:53客服系统通常支持网页、手机APP、微信等多种渠道的接入,源码中会有相应的接口实现,以确保用户可以通过不同的终端与客服进行交互...

    供应链管理系统源码

    供应链管理系统源码是一种用于管理企业内部以及与外部合作伙伴之间物流、信息流和资金流的软件解决方案。这个系统的核心目标是优化整个供应链流程,提高效率,降低成本,并确保在正确的时间、正确的地点提供正确的...

    Linux系统下dhcp源码

    Linux系统下的DHCP(Dynamic Host Configuration Protocol)源码解析 DHCP是一种网络协议,用于自动分配IP地址、子网掩码、默认网关等网络配置信息给网络中的设备。在Linux环境中,DHCP服务器通常使用isc-dhcp-...

    多功能在线报价系统源码

    【标题】:“多功能在线报价系统源码” 在线报价系统是一种基于网络的应用程序,它使得企业或个人能够方便快捷地提供产品或服务的价格信息给潜在客户。这个“多功能在线报价系统源码”是专为此目的设计的,允许用户...

Global site tag (gtag.js) - Google Analytics