RxSwift-如何用协议实现一个命名空间

RxSwift简介

RxSwift是一个函数响应式框架,使用到的是FRP(函数响应式编程)编程思想。它是通过构建函数操作数据序列,然后对这些序列做出响应的编程方式。基本的使用可以参考官方的RxSwift中文文档

rx是什么?

在RxSwift的使用过程中我们经常可以看到以下代码:

cameraButton.rx.tap
.bind(to: imageView.rx.image)
.disposed(by: disposeBag)

其中rx具体是什么呢?我们点击进去,找到以下源码

/// A type that has reactive extensions.
public protocol ReactiveCompatible: AnyObject {
/// associatedtype,用来实现protocol的泛型功能
associatedtype ReactiveBase: AnyObject

/// Reactive的类变量
static var rx: Reactive<ReactiveBase>.Type { get set }

/// Reactive的实例变量
var rx: Reactive<ReactiveBase> { get set }
}

// 通过extension protocol对两个rx变量提供默认get和set实现方法
extension ReactiveCompatible {
/// 类变量的set和get默认实现
public static var rx: Reactive<Self>.Type {
get { Reactive<Self>.self }

set { }
}

/// 实例变量的set和get默认实现
public var rx: Reactive<Self> {
get { Reactive(self) }

set { }
}
}

  1. rxprotocol ReactiveCompatible的属性,static var rx是类变量,var rx是实例变量,变量类型为Reactive<ReactiveBase>
  2. associatedtype关键字是为protocol提供泛型功能。Reactive遵循了ReactiveCompatible协议,那么Reactive的泛型就会替代ReactiveBase
  3. 在Swift中,我们可以通过extension protocol来为协议的属性或者方法提供默认实现。
public struct Reactive<Base: AnyObject> {
/// base存储泛型变量
public let base: Base

/// 构造方法
public init(_ base: Base) {
self.base = base
}
}

Reactive结构体有一个base属性,存储了当前的实例对象,方便调用Base的实例方法。

核心逻辑

  1. 定义一个协议ReactiveCompatible,协议拥有Reactive类型的属性rx

  2. NSObject遵循协议ReactiveCompatible,这样所有的类就都拥有了属性rx

    // NSObject遵循ReactiveCompatible协议,保证所有类都有rx属性
    extension NSObject: ReactiveCompatible { }
  3. 我们通常通过以下方式来使用RxSwift。当执行button.rx后,我们可以看到返回的类型为Reactive<UIButton>,所以我们要添加tap属性,扩展的应该是Reactive类。

    // RxSwift具体用法
    let button = UIButton()
    button.rx.tap
    .subscribe(onNext: {
    print("UIButton Tapped")
    })
    .disposed(by: disposeBag)
  4. 扩展Reactive,添加tap属性,where Base: UIButton表示此属性只是给UIButton添加。

    // 如果要对UIButton新增属性或者方法,是对Reactive结构体进行扩展,where语法限定扩展的属性或者方法只属于UIButton
    extension Reactive where Base: UIButton {

    public var tap: ControlEvent<Void> {
    controlEvent(.touchUpInside)
    }
    }

补充: Reactive结构体base属性的作用

URLSession扩展方法中,我们可以看到base属性的作用,主要是在调用URLSession自身的方法时用到。

extension Reactive where Base: URLSession {
public func response(request: URLRequest) -> Observable<(response: HTTPURLResponse, data: Data)> {
return Observable.create { observer in
let d: Date?

if URLSession.rx.shouldLogRequest(request) {
d = Date()
}
else {
d = nil
}

// 此时的base为URLSession的实例
let task = self.base.dataTask(with: request) { data, response, error in

if URLSession.rx.shouldLogRequest(request) {
let interval = Date().timeIntervalSince(d ?? Date())
print(convertURLRequestToCurlCommand(request))
#if os(Linux)
print(convertResponseToString(response, error.flatMap { $0 as NSError }, interval))
#else
print(convertResponseToString(response, error.map { $0 as NSError }, interval))
#endif
}

guard let response = response, let data = data else {
observer.on(.error(error ?? RxCocoaURLError.unknown))
return
}

guard let httpResponse = response as? HTTPURLResponse else {
observer.on(.error(RxCocoaURLError.nonHTTPResponse(response: response)))
return
}

observer.on(.next((httpResponse, data)))
observer.on(.completed)
}

task.resume()

return Disposables.create(with: task.cancel)
}
}
}