用Swift写一个响应式编程库。用Swift写一个响应式编程库。

2017年又赶紧过去了,忙了平等年感觉没有啥收获,感觉是匪是相应写点啥,想了好久从未想发而写啊。下半年因为工作的原故,狗狗也无养了,吉他上吧积满了灰尘,兴致勃勃的就学素描,到现为从未写有了啥,博客也颇遥远没更新了。想想觉得更新一下博客吧。

举2017年本人意以 Swift 进行开了。使用 Swift
进行开是一个杀高兴的经验,我曾经全无思再次去碰 OC
了。最近纪念做一个响应式编程的堆栈,所以便把它用来享受一下。

点击上“iOS开发”,选择“置顶公众号”

Reactive Programing

说及响应式编程,ReactiveCocoa 和 RxSwift 可以说凡是现阶段 iOS
开发中最为出彩的老三正在开源库了。今天咱们不聊 ReactiveCocoa 和
RxSwif,咱们自己来形容一个响应式编程库。如果你对观察者模式大熟稔的话,那么响应式编程就颇容易了解了。

响应式编程是一致种植面向数据流和生成传播之编程范式。

准用户输入、单击事件、变量值等还好看作一个注,你可洞察是流,并冲这个流做一些操作。“监听”流的一言一行称作订阅。响应式就是依据这种想法。

废话不多说,撸起袖子开干。

咱俩盖一个拿走用户信息的纱要为条例:

func fetchUser(with id: Int, completion: @escaping ((User) -> Void)) {
     DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()+2) {
         let user = User(name: "jewelz")
         completion(user)
     }
}

方是我们日常的做法,在请方法里流传一个回调函数,在回调里将到结果。在响应式里面,我们监听请求,当呼吁完成时,观察者得到更新。

func fetchUser(with id: Int) -> Signal<User> {}

出殡网络要虽好这样:

fetchUser(with: "12345").subscribe({

})

在得 Signal 之前,
需要定义订阅后回的数据结构,这里自己单关心成功与挫折两种状态的数量,所以可以这样描写:

enum Result<Value> {
    case success(Value)
    case error(Error)
}

今可起来兑现我们的 Signal 了:

final class Signal<Value> {
    fileprivate typealias Subscriber = (Result<Value>) -> Void
    fileprivate var subscribers: [Subscriber] = []

    func send(_ result: Result<Value>) {
        for subscriber in subscribers {
            subscriber(result)
        }
    }

    func subscribe(_ subscriber: @escaping (Result<Value>) -> Void) {
        subscribers.append(subscriber)
    }
}

形容单稍例子测试一下:

let signal = Signal<Int>()
signal.subscribe { result in
    print(result)
}
signal.send(.success(100))
signal.send(.success(200))

// Print
success(100)
success(200)

我们的 Signal
已经足以健康工作了,不过还有不少改进的上空,我们得行使一个厂方法来创造一个
Signal, 同时以 send成为私有的:

static func empty() -> ((Result<Value>) -> Void, Signal<Value>) {
     let signal = Signal<Value>()
     return (signal.send, signal)
}

fileprivate func send(_ result: Result<Value>) { ... }

如今我们需要如此使 Signal 了:

let (sink, signal) = Signal<Int>.empty()
signal.subscribe { result in
    print(result)
}
sink(.success(100))
sink(.success(200))

随着我们好吃 UITextField 绑定一个 Signal,只待以 Extension 中受
UITextField添加一个计算属性 :

extension UITextField {
    var signal: Signal<String> {
        let (sink, signal) = Signal<String>.empty()
        let observer = KeyValueObserver<String>(object: self, keyPath: #keyPath(text)) { str in
            sink(.success(str))
        }
        signal.objects.append(observer)
        return signal
    }
}

地方代码中之 observer 是一个有的变量,在
signal调用了晚,就会于销毁,所以用在 Signal 中保存该目标,可以给
Signal 添加一个数组,用来保存要延长生命周期的对象。 KeyValueObserver
是本着 KVO 的简包装,其落实如下:

final class KeyValueObserver<T>: NSObject {

    private let object: NSObject
    private let keyPath: String
    private let callback: (T) -> Void

    init(object: NSObject, keyPath: String, callback: @escaping (T) -> Void) {
        self.object = object
        self.keyPath = keyPath
        self.callback = callback
        super.init()
        object.addObserver(self, forKeyPath: keyPath, options: [.new], context: nil)
    }

    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
        guard let keyPath = keyPath, keyPath == self.keyPath, let value = change?[.newKey] as? T else { return }

        callback(value)
    }

    deinit {
        object.removeObserver(self, forKeyPath: keyPath)
    }
}

而今虽足以采取textField.signal.subscribe({}) 来观察 UITextField
内容之变动了。

每当 Playground 写单 VC 测试一下:

class VC {
    let textField =  UITextField()
    var signal: Signal<String>?

    func viewDidLoad() {
        signal = textField.signal
        signal?.subscribe({ result in
            print(result)
        })
        textField.text = "1234567"
    }

    deinit {
        print("Removing vc")
    }
}

var vc: VC? = VC()
vc?.viewDidLoad()
vc = nil

// Print
success("1234567")
Removing vc

关键时刻,第一时间送达!

Reference Cycles

自己在面的 Signal 中,添加了 deinit方法:

deinit {
    print("Removing Signal")
}

最后发现 Signal
的析构方法并没实施,也就是说上面的代码中冒出了循环引用,其实仔细分析者
UITextField 的拓蒙 signal的实现就能发现问题产生当哪儿了。

let observer = KeyValueObserver<String>(object: self, keyPath: #keyPath(text)) { str in
    sink(.success(str))
}

KeyValueObserver 的回调中,调用了 sink()方法,而 sink
方法其实就是是 signal.send(_:)措施,这里以闭包中抓获了signal
变量,于是便形成了巡回引用。这里要用 weak
就能迎刃而解。修改下之代码是如此的:

static func empty() -> ((Result<Value>) -> Void, Signal<Value>) {
     let signal = Signal<Value>()
     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<Value>) -> Void) -> Disposable {
     let token = UUID()
     subscribers[token] = subscriber
      return Disposable.create {
          self.subscribers[token] = nil
      }   
 }

如此这般我们如果在方便的火候销毁 Disposable 就可以移除观察者了。

作为一个响应式编程库都见面生出 map, flatMap, filter, reduce
等艺术,所以我们的堆栈也未可知少,我们可以简单的实现几乎只。

新萄京 1

map

map 比较简单,就是拿一个 返回值为包装值的函数
作用于一个包装(Wrapped)值的进程,
这里的包装值可以知道啊好分包其他价值的一样栽结构,例如 Swift
中之勤组,可选类型都是包装值。它们还起重载的 map,
flatMap抵函数。以数组为条例,我们经常这么以:

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

当今来促成我们的 map 函数:

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

本身还要被 Result 也实现了 map 函数:

extension Result {
    func map<T>(_ transform: @escaping (Value) -> T) -> Result<T> {
        switch self {
        case .success(let value):
            return .success(transform(value))
        case .error(let error):
            return .error(error)
        }
    }
}

// Test

let (sink, intSignal) = Signal<Int>.empty()
intSignal
    .map{ String($0)}
    .subscribe {  result in
        print(result)
}
sink(.success(100))

// Print success("100")

新萄京 2

flatMap

flatMap 和 map 很相似,但为发出有不等,以可选型为条例,Swif t是这样定义
map 与 flatMap 之:

public func map<U>(_ transform: (Wrapped) throws -> U) rethrows -> U?
public func flatMap<U>(_ transform: (Wrapped) throws -> U?) rethrows -> U?

flatMap 和 map 的异主要反映于 transform 函数的回到值不同。map
接受的函数返回值类型是 U品类,而 flatMap 接受的函数返回值类型是
U?项目。例如对于一个而摘值,可以如此调用:

let aString: String? = "¥99.9"
let price = aString.flatMap{ Float($0)}

// Price is nil

我们这里 flatMap 和 Swift 中数组以及可选型中之 flatMap 保持了平。

据此我们的 flatMap
应该是如此定义:flatMap<T>(_ transform: @escaping (Value) -> Signal<T>) -> Signal<T>

懂得了 flatMap 和 map 的异,实现起来呢即坏简短了:

func flatMap<T>(_ transform: @escaping (Value) -> Signal<T>) -> Signal<T> {
     let (sink, signal) = Signal<T>.empty()
     var _dispose: Disposable?
     let dispose = subscribe { (result) in
         switch result {
         case .success(let value):
             let new = transform(value)
             _dispose = new.subscribe({ _result in
                 sink(_result)
             })
         case .error(let error):
             sink(.error(error))
         }
    }
    if _dispose != nil {
        signal.objects.append(_dispose!)
    }
    signal.objects.append(dispose)
    return signal
}

现在我们好套一个网要来测试 flatMap:

func users() -> Signal<[User]> {
     let (sink, signal) = Signal<[User]>.empty()
     DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()+2) {
         let users = Array(1...10).map{ User(id: String(describing: $0)) }
         sink(.success(users))
     }
     return signal
 }

func userDetail(with id: String) -> Signal<User> {
    let (sink, signal) = Signal<User>.empty()
    DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()+2) {
        sink(.success(User(id: id, name: "jewelz")))
    }
    return signal
}

let dispose = users()
    .flatMap { return self.userDetail(with: $0.first!.id) }
    .subscribe { result in
        print(result)
}
disposes.append(dispose)

// Print: success(ReactivePrograming.User(name: Optional("jewelz"), id: "1"))

经过采用 flatMap ,我们得以非常简单的拿一个 Signal 转换为外一个 Signal ,
这当咱们处理多单请求嵌套时即见面特别方便了。

2017年还要赶忙过去了,忙了同样年感觉并未啥收获,感觉是免是应该写点吗,想了遥远不曾想发生要描写什么。下半年因做事之来由,狗狗为从来不留下了,吉他达成啊积满了灰尘,兴致勃勃的攻素描,到现在吗没画生了什么??,博客也异常漫长无更新了。想想觉得更新一下博客吧。

写于末

点通过100
多尽的代码就落实了一个简单易行的响应式编程库。不过对于一个仓房来说,以上之情节还远远不够。现在底
Signal
还不享有原子性,要作为一个实在可用的仓库,应该是线程安的。还有咱们针对
Disposable 的拍卖为不够优雅,可以如法炮制 RxSwift 中 DisposeBag
的做法。上面这些问题可留下读者自己去考虑了。(更多内容可翻自之主页)

整整2017年我了用 Swift
进行开了。使用 Swift 进行支付是一个格外欢乐的感受,我已经完全不思量还失去碰
OC 了。最近想做一个响应式编程的仓库,所以尽管把她以来分享一下。

Reactive
Programing

说到响应式编程,ReactiveCocoa 和 RxSwift
可以说凡是时 iOS 开发中最为地道之老三方开源库了。今天我们不聊
ReactiveCocoa 和
RxSwif,咱们自己来形容一个响应式编程库。如果你针对观察者模式大熟稔的话,那么响应式编程就非常容易了解了。

style=”font-size:15px;”>响应式编程是一样栽面向数据流和转传播的编程范式。

按部就班用户输入、单击事件、变量值等都得当一个注,你得考察这流,并因此流做一些操作。“监听”流的所作所为称为订阅。响应式就是根据这种想法。

废话不多说,撸起袖子开干。

俺们坐一个沾用户信息的大网要为例:

func fetchUser(with
id: Int, completion: @escaping ((User) -> Void)) {

   
 DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()+2)
{

         let user =
User(name: “jewelz”)

       
 completion(user)

     }

}

地方是咱司空见惯的做法,在伸手方法里传播一个回调函数,在回调里以到结果。在响应式里面,我们监听请求,当呼吁完成时,观察者得到更新。

func fetchUser(with
id: Int) -> Signal<User> {}

发送网络要虽好如此:

fetchUser(with:
“12345”).subscribe({

    

})

每当形成 Signal 之前,
需要定义订阅后回到的数据结构,这里自己就关心成功与挫折两种状态的数目,所以可以这样描写:

enum
Result<Value> {

    case
success(Value)

    case
error(Error)

}

本可起来兑现我们的 Signal 了:

final class
Signal<Value> {

    fileprivate
typealias Subscriber = (Result<Value>) -> Void

    fileprivate var
subscribers: [Subscriber] = []

  

    func send(_
result: Result<Value>) {

        for
subscriber in subscribers {

           
subscriber(result)

        }

    }

  

    func
subscribe(_ subscriber: @escaping (Result<Value>) -> Void)
{

       
subscribers.append(subscriber)

    }

}

形容单稍例子测试一下:

let signal =
Signal<Int>()

signal.subscribe {
result in

   
print(result)

}

style=”color:rgb(2,30,170);font-size:12px;”>signal.send(.success(100))

style=”color:rgb(2,30,170);font-size:12px;”>signal.send(.success(200))

// Print

success(100)

success(200)

我们的 Signal
已经得以健康工作了,不过还有不少改良的上空,我们得下一个厂方法来创造一个
Signal, 同时以 send变为私有的:

static func empty()
-> ((Result<Value>) -> Void, Signal<Value>) {

     let signal =
Signal<Value>()

     return
(signal.send, signal)

}

fileprivate func
send(_ result: Result<Value>) { … }

现今咱们要这么使 Signal 了:

let (sink, signal) =
Signal<Int>.empty()

signal.subscribe {
result in

   
print(result)

}

style=”font-size:12px;color:rgb(2,30,170);”>sink(.success(100))

style=”font-size:12px;color:rgb(2,30,170);”>sink(.success(200))

跟着我们得为 UITextField 绑定一个
Signal,只需要在 Extension 中叫 UITextField添加一个划算属性 :

extension
UITextField {

    var signal:
Signal<String> {

        let (sink,
signal) = Signal<String>.empty()

        let observer
= KeyValueObserver<String>(object: self, keyPath:
#keyPath(text)) { str in

           
sink(.success(str))

        }

       
signal.objects.append(observer)

        return
signal

    }

}

方代码中的 observer 是一个片变量,在
signal调用了后,就会见给灭绝,所以待以 Signal 中保留该目标,可以于
Signal 添加一个数组,用来保存得延长生命周期的靶子。 KeyValueObserver
是对 KVO 的简易包装,其实现如下:

final class
KeyValueObserver<T>: NSObject {

    

    private let
object: NSObject

    private let
keyPath: String

    private let
callback: (T) -> Void

    

    init(object:
NSObject, keyPath: String, callback: @escaping (T) -> Void)
{

        self.object
= object

        self.keyPath
= keyPath

       
self.callback = callback

       
super.init()

       
object.addObserver(self, forKeyPath: keyPath, options: [.new],
context: nil)

    }

    

    override func
observeValue(forKeyPath keyPath: String?, of object: Any?, change:
[NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?)
{

        guard let
keyPath = keyPath, keyPath == self.keyPath, let value =
change?[.newKey] as? T else { return }

      

       
callback(value)

    }

    

    deinit {

       
object.removeObserver(self, forKeyPath: keyPath)

    }

}

今就算足以行使textField.signal.subscribe({})
来观察 UITextField 内容的变动了。

以 Playground 写个 VC 测试一下:

class VC {

    let textField =
 UITextField()

    var signal:
Signal<String>?

    

    func
viewDidLoad() {

        signal =
textField.signal

       
signal?.subscribe({ result in

           
print(result)

        })

       
textField.text = “1234567”

    }

    

    deinit {

       
print(“Removing vc”)

    }

}

var vc: VC? =
VC()

style=”font-size:12px;color:rgb(2,30,170);”>vc?.viewDidLoad()

vc = nil

// Print

style=”font-size:12px;color:rgb(2,30,170);”>success(“1234567”)

Removing vc

Reference
Cycles

自己于方的 Signal 中,添加了
deinit方法:

deinit {

    print(“Removing
Signal”)

}

最终发现 Signal
的析构方法并从未履行,也就是说上面的代码中起了循环引用,其实仔细分析者
UITextField 的展开着 signal的实现即能够窥见问题产生以何处了。

let observer =
KeyValueObserver<String>(object: self, keyPath: #keyPath(text))
{ str in

   
sink(.success(str))

}

在 KeyValueObserver 的回调中,调用了
sink()方法,而 sink 方法其实就是
signal.send(_:)方法,这里以闭包中抓获了signal
变量,于是就形成了巡回引用。这里而利用 weak
就会化解。修改下的代码是这般的:

static func empty()
-> ((Result<Value>) -> Void, Signal<Value>) {

     let signal =
Signal<Value>()

     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<Value>) -> 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<T>(_
transform: @escaping (Value) -> T) -> Signal<T> {

     let (sink,
signal) = Signal<T>.empty()

     let dispose =
subscribe { (result) in

         
sink(result.map(transform))

      }

     
signal.objects.append(dispose)

      return
signal

}

本人又为 Result 也落实了 map 函数:

extension Result
{

    func
map<T>(_ transform: @escaping (Value) -> T) ->
Result<T> {

        switch self
{

        case
.success(let value):

            return
.success(transform(value))

        case
.error(let error):

            return
.error(error)

        }

    }

}

// Test

let (sink,
intSignal) = Signal<Int>.empty()

intSignal

    .map{
String($0)}

    .subscribe {
 result in

       
print(result)

}

style=”font-size:12px;color:rgb(2,30,170);”>sink(.success(100))

// Print
success(“100”)

flatMap

flatMap 和 map
很相似,但为生有见仁见智,以可选型为条例,Swif t是如此定义 map 与 flatMap
之:

public func
map<U>(_ transform: (Wrapped) throws -> U) rethrows ->
U?

public func
flatMap<U>(_ transform: (Wrapped) throws -> U?) rethrows
-> U?

flatMap 和 map 的差主要体现在 transform
函数的归来值不同。map 接受之函数返回值类型是 U类型,而 flatMap
接受的函数返回值类型是 U?类型。例如对于一个而摘值,可以这么调用:

let aString: String?
= “¥99.9”

let price =
aString.flatMap{ Float($0)}

// Price is
nil

我们这里 flatMap 和 Swift
中数组与可选型中之 flatMap 保持了同样。

之所以我们的 flatMap
应该是这样定义:flatMap<T>(_ transform: @escaping (Value) ->
Signal<T>) -> Signal<T> 。

知道了 flatMap 和 map
的异,实现起来也就很简短了:

func
flatMap<T>(_ transform: @escaping (Value) ->
Signal<T>) -> Signal<T> {

     let (sink,
signal) = Signal<T>.empty()

     var _dispose:
Disposable?

     let dispose =
subscribe { (result) in

         switch
result {

         case
.success(let value):

             let new
= transform(value)

           
 _dispose = new.subscribe({ _result in

               
 sink(_result)

           
 })

         case
.error(let error):

           
 sink(.error(error))

         }

    }

    if _dispose !=
nil {

       
signal.objects.append(_dispose!)

    }

   
signal.objects.append(dispose)

    return
signal

}

当今咱们可学一个网要来测试
flatMap:

func users() ->
Signal<[User]> {

     let (sink,
signal) = Signal<[User]>.empty()

   
 DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()+2)
{

         let users =
Array(1…10).map{ User(id: String(describing: $0)) }

       
 sink(.success(users))

     }

     return
signal

 }

    

func userDetail(with
id: String) -> Signal<User> {

    let (sink,
signal) = Signal<User>.empty()

   
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()+2) {

       
sink(.success(User(id: id, name: “jewelz”)))

    }

    return
signal

}

let dispose =
users()

    .flatMap {
return self.userDetail(with: $0.first!.id) }

    .subscribe {
result in

       
print(result)

}

style=”font-size:12px;color:rgb(2,30,170);”>disposes.append(dispose)

// Print:
success(ReactivePrograming.User(name: Optional(“jewelz”), id:
“1”))

经过行使 flatMap ,我们得很简单的用一个
Signal 转换为外一个 Signal ,
这在我们处理多个请求嵌套时就见面好便宜了。

写以最后

地方通过100
多行之代码就贯彻了一个略的响应式编程库。不过对此一个库房来说,以上的内容还远不够。现在之
Signal
还不拥有原子性,要作一个实在可用之堆栈,应该是线程安的。还有我们对
Disposable 的拍卖为不够优雅,可以学 RxSwift 中 DisposeBag
的做法。上面这些题材得以留给读者自己去想了。

新萄京 3

  • 来自:huluobobo

  • 链接:http://www.jianshu.com/p/b7ebf42a620a

  • iOS开发整理公布,转载请联系作者授权

新萄京 4

新萄京 5【点击成为Android大神】

发表评论

电子邮件地址不会被公开。 必填项已用*标注