亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 編程 > Swift > 正文

利用Swift實現一個響應式編程庫

2020-03-09 17:43:00
字體:
來源:轉載
供稿:網友

前言

整個2017年我完全使用 Swift 進行開發了。使用 Swift 進行開發是一個很愉快的體驗,我已經完全不想再去碰 OC 了。最近想做一個響應式編程的庫,所以就把它拿來分享一下。

在缺乏好的資源的情況下,學習響應式編程成為痛苦。我開始學的時候,做死地找各種教程。結果發現有用的只是極少部分,而且這少部分也只是表面上的東西,對于整個體系結構的理解也起不了多大的作用。

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 {}

發送網絡請求就可以這樣:

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

在完成 Signal 之前, 需要定義訂閱后返回的數據結構,這里我只關心成功和失敗兩種狀態的數據,所以可以這樣寫:

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

現在可以開始實現我們的 Signal 了:

final class Signal { fileprivate typealias Subscriber = (Result) -> Void fileprivate var subscribers: [Subscriber] = []  func send(_ result: Result) { for subscriber in subscribers {  subscriber(result) } }  func subscribe(_ subscriber: @escaping (Result) -> Void) { subscribers.append(subscriber) }}

寫個小例子測試一下:

let signal = Signal()signal.subscribe { result in print(result)}signal.send(.success(100))signal.send(.success(200))// Printsuccess(100)success(200)

我們的 Signal 已經可以正常工作了,不過還有很多改進的空間,我們可以使用一個工廠方法來創建一個 Signal, 同時將 send變為私有的:

static func empty() -> ((Result) -> Void, Signal) { let signal = Signal() return (signal.send, signal)}fileprivate func send(_ result: Result) { ... }

現在我們需要這樣使用 Signal 了:

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

接著我們可以給 UITextField 綁定一個 Signal,只需要在 Extension 中給 UITextField添加一個計算屬性 :

extension UITextField { var signal: Signal { let (sink, signal) = Signal.empty() let observer = KeyValueObserver(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: 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?  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// Printsuccess("1234567")Removing vc

Reference Cycles

我在上面的 Signal 中,添加了 deinit方法:

deinit { print("Removing Signal")}

最后發現 Signal 的析構方法并沒有執行,也就是說上面的代碼中出現了循環引用,其實仔細分析上面 UITextField 的拓展中 signal的實現就能發現問題出在哪兒了。

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

在 KeyValueObserver 的回調中,調用了 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 = UUIDfileprivate 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) } }}// Testlet (sink, intSignal) = Signal.empty()intSignal .map{ String($0)} .subscribe { result in print(result)}sink(.success(100))// Print success("100")

flatMap

flatMap 和 map 很相似,但也有一些不同,以可選型為例,Swif t是這樣定義 map 和 flatMap 的:

public func map(_ transform: (Wrapped) throws -> U) rethrows -> U?public func flatMap(_ 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(_ transform: @escaping (Value) -> Signal) -> Signal。

理解了 flatMap 和 map 的不同,實現起來也就很簡單了:

func flatMap(_ transform: @escaping (Value) -> Signal) -> Signal { let (sink, signal) = Signal.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 { let (sink, signal) = Signal.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 { let (sink, signal) = Signal.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 , 這在我們處理多個請求嵌套時就會很方便了。

寫在最后

上面通過100 多行的代碼就實現了一個簡單的響應式編程庫。不過對于一個庫來說,以上的內容還遠遠不夠?,F在的 Signal 還不具有原子性,要作為一個實際可用的庫,應該是線程安的。還有我們對 Disposable 的處理也不夠優雅,可以模仿 RxSwift 中 DisposeBag 的做法。上面這些問題可以留給讀者自己去思考了。

好了,以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對VEVB武林網的支持。


注:相關教程知識閱讀請移步到swift教程頻道。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
2020久久国产精品| 国产精品九九九| 久久精品国产一区二区电影| 久久久久免费精品国产| 久久精品精品电影网| 午夜精品久久久久久久久久久久| 57pao成人永久免费视频| 亚洲国产精品va在线看黑人| 51久久精品夜色国产麻豆| 91av视频在线| 国产视频在线观看一区二区| 最近2019好看的中文字幕免费| 亚洲成人性视频| 久久精品国产一区二区三区| 国产精品精品国产| 久久精品久久久久电影| 成人久久久久久久| 蜜月aⅴ免费一区二区三区| 在线视频亚洲欧美| 91在线看www| 精品无人国产偷自产在线| 国产成人精品av在线| 亚洲欧美日韩久久久久久| 日韩在线视频导航| 日韩精品视频在线播放| 日韩亚洲综合在线| 亚洲欧洲美洲在线综合| 精品美女永久免费视频| 欧美成人午夜剧场免费观看| 成人字幕网zmw| 欧美另类69精品久久久久9999| 97色在线视频观看| 亚洲精品免费在线视频| 亚洲欧美日韩网| 一区二区av在线| 色悠悠国产精品| 国产成人精品免费视频| 国产精品丝袜白浆摸在线| 日韩电影免费观看中文字幕| 亚洲人精品午夜在线观看| 国产日韩欧美在线视频观看| 2019av中文字幕| 精品亚洲一区二区三区在线播放| 日韩成人av网| 永久免费精品影视网站| 久久久久久这里只有精品| 日韩有码在线视频| 91tv亚洲精品香蕉国产一区7ujn| 欧美视频一二三| 日韩中文在线视频| 97人人模人人爽人人喊中文字| 欧美极度另类性三渗透| 亚洲精品美女在线观看播放| 久久中文字幕在线| 亚洲黄色免费三级| 久久久久久亚洲精品不卡| 亚洲精品在线视频| 亚洲精品视频在线观看视频| 2019亚洲日韩新视频| 高清一区二区三区四区五区| 国语自产精品视频在线看一大j8| 日韩在线视频网站| 亚洲成人精品在线| 成人免费黄色网| 欧美成年人网站| 欧美高清视频在线观看| 不用播放器成人网| 国产精品电影久久久久电影网| 亚洲国产黄色片| 激情亚洲一区二区三区四区| 成人h猎奇视频网站| 亚洲天堂第一页| 青草青草久热精品视频在线网站| 日韩大陆毛片av| 亚洲精品97久久| 亚洲午夜av电影| 国产精品久久久久免费a∨大胸| 久久不射热爱视频精品| 欧美尺度大的性做爰视频| 久久综合88中文色鬼| 69av在线播放| 欧美成人免费va影院高清| 亚洲精品美女免费| 国产精品成人一区二区三区吃奶| 欧美孕妇性xx| 日韩亚洲精品电影| 日韩亚洲欧美中文高清在线| 91免费的视频在线播放| 91久久嫩草影院一区二区| 在线观看精品国产视频| 国产91色在线|| 久久精品成人欧美大片古装| 在线播放日韩精品| 亚洲激情小视频| 欧美精品videossex88| 日韩黄在线观看| 久久视频精品在线| 亚洲第一色在线| 久久6免费高清热精品| 亚洲国产日韩欧美在线图片| 日韩亚洲欧美中文在线| 国产日本欧美在线观看| 成人性生交大片免费看小说| 日韩精品极品视频| 欧美韩日一区二区| 日韩在线视频免费观看| 日韩成人高清在线| 日韩高清免费观看| 亚洲天堂日韩电影| 久久成人亚洲精品| 精品国产乱码久久久久久虫虫漫画| 日韩av影视综合网| 欧美激情2020午夜免费观看| 精品中文视频在线| 91久久精品美女| 岛国视频午夜一区免费在线观看| 国产精品成人一区| 欧美激情性做爰免费视频| 精品毛片网大全| 欧美一区二区三区艳史| 久久国产精品久久久久| 九九综合九九综合| 国模吧一区二区| 亚洲男人第一网站| 91视频免费网站| 一区三区二区视频| 97国产精品视频| 毛片精品免费在线观看| 亚洲免费人成在线视频观看| 奇门遁甲1982国语版免费观看高清| 久久久久在线观看| 国产精品都在这里| 精品高清一区二区三区| 亚洲人成电影在线| 成人午夜在线观看| 日韩精品视频观看| 国产一区私人高清影院| 欧美国产在线电影| 2019中文字幕免费视频| 91网站在线免费观看| 久久久亚洲国产天美传媒修理工| 久久人91精品久久久久久不卡| 懂色av影视一区二区三区| 欧美午夜女人视频在线| 国产欧美在线看| 亚洲色图狂野欧美| 国产性猛交xxxx免费看久久| 伊人久久精品视频| 亚洲字幕一区二区| 亚洲欧美日韩中文视频| 精品magnet| 中文字幕日韩欧美在线| 久久在线精品视频| 国内精品模特av私拍在线观看| 亚洲国产福利在线| 最好看的2019的中文字幕视频| 国产精品亚洲欧美导航| 中文字幕日韩精品在线| 久久精品视频在线观看| 欧美日韩亚洲高清| 欧美日韩国内自拍| 精品久久国产精品| 精品国产一区久久久| 国产不卡精品视男人的天堂|