利用Swift实现一个响应式编程库

2020-01-09 00:04:54丽君
sink()方法,而 sink 方法其实就是 signal.send(_:)方法,这里在闭包中捕获了signal 变量,于是就形成了循环引用。这里只要使用 weak 就能解决。修改下的代码是这样的:


static func empty() -> ((Result) -> Void, Signal) {
 let signal = Signal()
 return ({[weak signal] value in signal?.send(value)}, signal)
}

再次运行, Signal 的析构方法就能执行了。

上面就实现了一个简单的响应式编程的库了。不过这里还存在很多问题,比如我们应该在适当的时机移除观察者,现在我们的观察者被添加在 subscribers 数组中,这样就不知道该移除哪一个观察者,所以我们将数字替换成字典,用 UUID 作为 key :


fileprivate typealias Token = UUID
fileprivate var subscribers: [Token: Subscriber] = [:]

我们可以模仿 RxSwift 中的 Disposable 用来移除观察者,实现代码如下:


final class Disposable {
 private let dispose: () -> Void
 
 static func create(_ dispose: @escaping () -> Void) -> Disposable {
 return Disposable(dispose)
 } 
 init(_ dispose: @escaping () -> Void) {
 self.dispose = dispose
 } 
 deinit {
 dispose()
 }
}

原来的 subscribe(_:) 返回一个 Disposable 就可以了:


func subscribe(_ subscriber: @escaping (Result) -> Void) -> Disposable {
 let token = UUID()
 subscribers[token] = subscriber
 return Disposable.create {
  self.subscribers[token] = nil
 } 
 }

这样我们只要在适当的时机销毁 Disposable 就可以移除观察者了。

作为一个响应式编程库都会有 map, flatMap, filter, reduce 等方法,所以我们的库也不能少,我们可以简单的实现几个。

map

map 比较简单,就是将一个 返回值为包装值的函数 作用于一个包装(Wrapped)值的过程, 这里的包装值可以理解为可以包含其他值的一种结构,例如 Swift 中的数组,可选类型都是包装值。它们都有重载的 map, flatMap等函数。以数组为例,我们经常这样使用:


let images = ["1", "2", "3"].map{ UIImage(named: $0) }

现在来实现我们的 map 函数:


func map(_ transform: @escaping (Value) -> T) -> Signal {
 let (sink, signal) = Signal.empty()
 let dispose = subscribe { (result) in
  sink(result.map(transform))
 }
 signal.objects.append(dispose)
 return signal
}

我同时给 Result 也实现了 map 函数:


extension Result {
 func map(_ transform: @escaping (Value) -> T) -> Result {
 switch self {
 case .success(let value):
  return .success(transform(value))
 case .error(let error):
  return .error(error)
 }
 }
}
// Test
let (sink, intSignal) = Signal.empty()
intSignal
 .map{ String($0)}
 .subscribe { result in
 print(result)
}
sink(.success(100))
// Print success("100")