小松的技术博客

六和敬

若今生迷局深陷,射影含沙。便许你来世袖手天下,一幕繁华。 你可愿转身落座,掌间朱砂,共我温酒煮茶。

SwiftBond源码解析(二)

在上一篇文章中,我们基本上了解了SwiftBond的事件订阅-发布的框架实现,这篇文章就主要聚焦于SwiftBond在UIKit上的扩展以及我们如何在开发中使用SwiftBond。

在学习SwiftBond在UIKit上的扩展时,我们也需要了解其所运用到的相关知识点:

Associated Objects

我们知道,在extension一个对象时是不能携带stored properties的,如果你这样做了,你会得到一个错误提示:

但我们依旧想在extenstion上存储一些值得的时候,我们该怎么做呢?答案就是Associated Objects ,Associated Objects是OC提供的运行时对已经存在的类在扩展中添加自定义的属性的能力,在swift中,我们可以针对NSObject以及其子类使用。具体使用方法我们可以学习Nshipster上的文章(传送门: Associated Objects中文翻译)。

下面来看看SwiftBond中在NSObject上扩展时对Associated Objects的运用:

private struct AssociatedKeys {
    static var DisposeBagKey = "bnd_DisposeBagKey"
    static var AssociatedObservablesKey = "bnd_AssociatedObservablesKey"
}

首先SwiftBond用了一个struct来指明关联属性的key

public var bnd_bag: DisposeBag {
    if let disposeBag: AnyObject = objc_getAssociatedObject(self, &AssociatedKeys.DisposeBagKey) {
      return disposeBag as! DisposeBag
    } else {
      let disposeBag = DisposeBag()
      objc_setAssociatedObject(self, &AssociatedKeys.DisposeBagKey, disposeBag, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
      return disposeBag
   }
}

DisposeBag的作用与CompositeDisposable作用类似,不过DisposeBag只是一个简单的容器,并且在deinit时会调用dispose

这里用Associated Objects给NSObject的实例关联了一个DisposeBag实例,而Nshipster上的文章也有提到:

被关联的对象在生命周期内要比对象本身释放的晚很多。它们会在被 NSObject -dealloc 调用的 object_dispose() 方法中释放。

知道这个我们就可以清楚的知道DisposeBagdeinit方法会何时调用了, 如下面的使用:

class MyViewController: UIViewController {

  override func viewDidLoad() {
    super.viewDidLoad()

    viewModel.name
      .observe { name in
        print(name)
      }
      .disposeIn(bnd_bag)
  }
}

我们已经知道observe返回的是关联EventProducer和事件订阅者的DisableType了,那我们将其加入MyViewController所关联的DisposeBag,因而在viewController释放时这次订阅关系也会结束,这正是我们所期待的。

我们来看看另一个Associated Objects的使用

internal var bnd_associatedObservables: [String:AnyObject] {
    get {
      return objc_getAssociatedObject(self, &AssociatedKeys.AssociatedObservablesKey) as? [String:AnyObject] ?? [:]
    }
    set(observable) {
      objc_setAssociatedObject(self, &AssociatedKeys.AssociatedObservablesKey, observable, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
    }
}

这也是在NSObject上的扩展,关联的是一个[String:AnyObject]字典,它的作用从其名字可以知道,使用来关联Observable的,我们来看看"绑定"的实现.

绑定

框架继续在NSObject上扩展来实现相关功能,我们重点关注下面那个方法:

public func bnd_associatedObservableForValueForKey<T>(key: String, initial: T? = nil, set: (T -> Void)? = nil) -> Observable<T> {
    if let observable: AnyObject = bnd_associatedObservables[key] {
      return observable as! Observable<T>
    } else {
      let observable = Observable<T>(initial ?? self.valueForKey(key) as! T)
      bnd_associatedObservables[key] = observable

      observable
        .observeNew { [weak self] (value: T) in
          if let set = set {
            set(value)
          } else {
            if let value = value as? AnyObject {
              self?.setValue(value, forKey: key)
            } else {
              self?.setValue(nil, forKey: key)
            }
          }
        }

      return observable
    }
}

这个方法返回的是Observable,根据if-else逻辑我们可以知道之前bnd_associatedObservables的作用是根据传入的key来缓存生成的Observable。我们直接看else逻辑:

observable
.observeNew { [weak self] (value: T) in
    if let set = set {
        set(value)
    } else {
        if let value = value as? AnyObject {
            self?.setValue(value, forKey: key)
        } else {
            self?.setValue(nil, forKey: key)
        }
    }
}

这里说明了这个observable的职责:其事件订阅者就是在接收事件时调用setValue:forKey:为NSObject的相关属性赋值

如果去看其对UIKit的扩展时,我们都可以看到其基本类似,都是调用了bnd_associatedObservableForValueForKey方法,比如UILabel:

extension UILabel {

  public var bnd_text: Observable<String?> {
    return bnd_associatedObservableForValueForKey("text")
  }

  public var bnd_attributedText: Observable<NSAttributedString?> {
    return bnd_associatedObservableForValueForKey("attributedText")
  }

  public var bnd_textColor: Observable<UIColor?> {
    return bnd_associatedObservableForValueForKey("textColor")
 }

}

现在我们可以来解释下viewModel.numberOfFollowers.bindTo(label.bnd_text)会发生什么了:

  • label.bnd_text实际上回返回一个Observable,当作为bindTo的参数时,它会作为BindableType存在
  • bindTo会连接viewModel.numberOfFollowers(EventProducer)和label.bnd_text(BindableType),因此viewModel.numberOfFollowers的变化值会传递给label.bnd_text
  • label.bnd_text的事件订阅者又会调用setValue:forKey:将值赋给UILabeltext属性,这样就完成了绑定工作。

但这样是是实现了EventProducer到View的绑定,那么我们如何监听一些可输入view如UITextField的输入呢?我们去看看UITextField的扩展:

public var bnd_text: Observable<String?> {
    if let bnd_text: AnyObject = objc_getAssociatedObject(self, &AssociatedKeys.TextKey) {
      return bnd_text as! Observable<String?>
    } else {
      let bnd_text = Observable<String?>(self.text)
      objc_setAssociatedObject(self, &AssociatedKeys.TextKey, bnd_text, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)

      var updatingFromSelf: Bool = false

      bnd_text.observeNew { [weak self] (text: String?) in
        if !updatingFromSelf {
          self?.text = text
        }
      }

      self.bnd_controlEvent.filter { $0 == UIControlEvents.EditingChanged }.observe { [weak self, weak bnd_text] event in
        guard let unwrappedSelf = self, let bnd_text = bnd_text else { return }
        updatingFromSelf = true
        bnd_text.next(unwrappedSelf.text)
        updatingFromSelf = false
      }

      return bnd_text
    }
}

其Observable的逻辑与UILabel的类似,但是多了对self.bnd_controlEvent的处理,我们去UIControl+Bond去看看其是什么:

public var bnd_controlEvent: EventProducer<UIControlEvents> {
    if let bnd_controlEvent: AnyObject = objc_getAssociatedObject(self, &AssociatedKeys.ControlEventKey) {
      return bnd_controlEvent as! EventProducer<UIControlEvents>
    } else {
      var capturedSink: (UIControlEvents -> Void)! = nil

      let bnd_controlEvent = EventProducer<UIControlEvents> { sink in
        capturedSink = sink
        return nil
      }

      let controlHelper = UIControlBondHelper(control: self, sink: capturedSink)

      objc_setAssociatedObject(self, &AssociatedKeys.ControlBondHelperKey, controlHelper, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
      objc_setAssociatedObject(self, &AssociatedKeys.ControlEventKey, bnd_controlEvent, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
      return bnd_controlEvent
    }
}

原来其返回了一个EventProducer,这个EventProducer是用来派发UIControl事件的,首先利用capturedSink捕获传入EventProducerproducer,按照上一篇文章的分析,EventProducer会把producer产生的事件派发给自己的事件订阅者。而这个producer又会产生什么事件呢?我们看UIControlBondHelper这个类:

@objc class UIControlBondHelper: NSObject{
  weak var control: UIControl?
  let sink: UIControlEvents -> Void

  init(control: UIControl, sink: UIControlEvents -> Void) {
    self.control = control
    self.sink = sink
    super.init()
    control.addTarget(self, action: #selector(UIControlBondHelper.eventHandlerTouchDown), forControlEvents: UIControlEvents.TouchDown)
    ...
  }

  func eventHandlerTouchDown() {
    sink(.TouchDown)
  }
  ...
}

这里是通过Target-Action的方式捕获事件并传递给了bnd_controlEvent的事件订阅者。这整个逻辑涉及到了高阶函数等知识,可能理解起来比较麻烦,读者可以仔细品味一下。

UITextFiledbnd_text获取中,会为self_controlEvent添加事件订阅者,我们看看其实现:

self.bnd_controlEvent.filter { $0 == UIControlEvents.EditingChanged }.observe { [weak self, weak bnd_text] event in
    guard let unwrappedSelf = self, let bnd_text = bnd_text else { return }
    updatingFromSelf = true
    bnd_text.next(unwrappedSelf.text)
    updatingFromSelf = false
}

这里只关注UIControlEvents.EditingChanged,然后会将此刻的text属性的值派发出去,但这时会有一个updatingFromSelf标记位,这是由于我们一开始为btn_text添加了个事件订阅者用于在接收事件时更新自己,但此时的事件是由于更新自己而发出去的,所以不能让这个订阅者做出任何反应,否则会造成死循环。

如此,我们就能理解下面的例子的实现细节了:

textField.bnd_text.bindTo(label.bnd_text)

双向绑定

SwiftBond为我们提供了两个操作符来表示双向绑定与单向绑定。没什么难度,我们看看即可:

infix operator ->> { associativity left precedence 95 } // 单向绑定
infix operator ->>< { associativity left precedence 95 } // 双向绑定

public func ->> <O: EventProducerType, B: BindableType where B.Element == O.EventType>(source: O, destination: B) -> DisposableType {
  return source.bindTo(destination)
}

public func ->> <O: EventProducerType, B: BindableType where B.Element == Optional<O.EventType>>(source: O, destination: B) -> DisposableType {
  return source.bindTo(destination)
}

public func ->>< <B: BindableType where B: EventProducerType, B.Element == B.EventType>(source: B, destination: B) -> DisposableType {
  let d1 = source.bindTo(destination)
  let d2 = destination.bindTo(source)
  return CompositeDisposable([d1, d2])
}

SwiftBond在KVO上的扩展:

KVO,即:Key-Value Observing,它提供一种机制,当指定的对象的属性被修改后,则对象就会接受到通知。简单的说就是每次指定的被观察的对象的属性被修改后,KVO就会自动通知相应的观察者了。这是系统框架提供给开发者的一个观察者框架,其使用也很简单

  1. 注册,指定被观察者的属性;
  2. 实现回调方法;
  3. 移除观察.

在SwiftBond中继承NSObject封装了一个BNDKVOObserver类用于简化这三个步骤:

@objc private class BNDKVOObserver: NSObject {
  static private var XXContext = 0

  var object: NSObject
  let keyPath: String
  let listener: AnyObject? -> Void

  init(object: NSObject, keyPath: String, options: NSKeyValueObservingOptions, listener: AnyObject? -> Void) {
    self.object = object
    self.keyPath = keyPath
    self.listener = listener
    super.init()
    // 1. 注册,指定被观察者的属性
    self.object.addObserver(self, forKeyPath: keyPath, options: options, context: &BNDKVOObserver.XXContext)
  }

  func set(value: AnyObject?) {
    object.setValue(value, forKey: keyPath)
  }
  //3. 移除观察.
  deinit {
    object.removeObserver(self, forKeyPath: keyPath)
  }
  // 2. 实现回调方法;
  override dynamic func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
    if context == &BNDKVOObserver.XXContext {
      if let newValue: AnyObject? = change?[NSKeyValueChangeNewKey] {
        listener(newValue)
      }
    }
  }
}

然后框架对Observable进行了扩展:

public extension Observable {

  public convenience init(object: NSObject, keyPath: String) {
    if let value = object.valueForKeyPath(keyPath) as? Wrapped {
      self.init(value)
    } else {
      ...
    }

    var updatingFromSelf = false

    let observer = BNDKVOObserver(object: object, keyPath: keyPath, options: .New) { [weak self] value in
      updatingFromSelf = true
      if let value = value as? EventType {
        self?.value = value
      } else {
        ...
      }
      updatingFromSelf = false
    }

    observeNew { value in
      if !updatingFromSelf {
        observer.set(value as? AnyObject)
      }
    }
  }
}

这里的扩展为Observable添加了一个convenience init方法,其用observeNew直接注册了一个事件订阅者,其接收到消息后会调用observer.set(value as? AnyObject),而这里的observer就是之前提到过的BNDKVOObserver的实例。这里注意updatingFromSelf这个标记位,因为observer.set(value as? AnyObject)会对self?.value进行赋值,而这又会触发事件订阅者,因此需要加上它避免死循环。

通过这个Observable的扩展,我们就可以方便的把KVO桥接到SwiftBond上了。

本文主要解读了SwiftBond在UIKit的扩展上的一些行为并简要分析了几个简单的使用例子,SwiftBond对其它很多UIKit组件的扩展都大同小异,但在UIKit存在两个比较特殊的组件UITableView和UICollectionView,那么SwiftBond又会针对它们做怎样的扩展呢?且听下回分解。

←支付宝← →微信 →
comments powered by Disqus