最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

利用Swift實現(xiàn)一個響應式編程庫

 更新時間:2017年12月09日 10:08:48   作者:huluobobo  
最近在學習swift,最近有空所以總結一下最近學習的內(nèi)容,下面這篇文章主要給大家介紹了關于利用Swift實現(xiàn)一個響應式編程庫的相關資料,文中通過示例代碼介紹的非常詳細,需要的朋友可以參考借鑒,下面來一起看看吧。

前言

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

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

Reactive Programing

說到響應式編程,ReactiveCocoa 和 RxSwift 可以說是目前 iOS 開發(fā)中最優(yōu)秀的第三方開源庫了。今天咱們不聊 ReactiveCocoa 和 RxSwif,咱們自己來寫一個響應式編程庫。如果你對觀察者模式很熟悉的話,那么響應式編程就很容易理解了。

響應式編程是一種面向數(shù)據(jù)流和變化傳播的編程范式。

比如用戶輸入、單擊事件、變量值等都可以看做一個流,你可以觀察這個流,并基于這個流做一些操作?!氨O(jiān)聽”流的行為叫做訂閱。響應式就是基于這種想法。

廢話不多說,擼起袖子開干。

我們以一個獲取用戶信息的網(wǎng)絡請求為例:

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

上面是我們通常的做法,在請求方法里傳入一個回調(diào)函數(shù),在回調(diào)里拿到結果。在響應式里面,我們監(jiān)聽請求,當請求完成時,觀察者得到更新。

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

發(fā)送網(wǎng)絡請求就可以這樣:

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

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

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

現(xiàn)在可以開始實現(xiàn)我們的 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))
// Print
success(100)
success(200)

我們的 Signal 已經(jīng)可以正常工作了,不過還有很多改進的空間,我們可以使用一個工廠方法來創(chuàng)建一個 Signal, 同時將 send變?yōu)樗接械模?/p>

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

現(xiàn)在我們需要這樣使用 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調(diào)用完后,就會被銷毀,所以需要在 Signal 中保存該對象,可以給 Signal 添加一個數(shù)組,用來保存需要延長生命周期的對象。 KeyValueObserver 是對 KVO 的簡單封裝,其實現(xiàn)如下:

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

現(xiàn)在就可以使用textField.signal.subscribe({}) 來觀察 UITextField 內(nèi)容的改變了。

在 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
// Print
success("1234567")
Removing vc

Reference Cycles

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

deinit {
 print("Removing Signal")
}

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

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

在 KeyValueObserver 的回調(diào)中,調(diào)用了 sink()方法,而 sink 方法其實就是 signal.send(_:)方法,這里在閉包中捕獲了signal 變量,于是就形成了循環(huán)引用。這里只要使用 weak 就能解決。修改下的代碼是這樣的:

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

再次運行, Signal 的析構方法就能執(zhí)行了。

上面就實現(xiàn)了一個簡單的響應式編程的庫了。不過這里還存在很多問題,比如我們應該在適當?shù)臅r機移除觀察者,現(xiàn)在我們的觀察者被添加在 subscribers 數(shù)組中,這樣就不知道該移除哪一個觀察者,所以我們將數(shù)字替換成字典,用 UUID 作為 key :

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

我們可以模仿 RxSwift 中的 Disposable 用來移除觀察者,實現(xiàn)代碼如下:

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

這樣我們只要在適當?shù)臅r機銷毀 Disposable 就可以移除觀察者了。

作為一個響應式編程庫都會有 map, flatMap, filter, reduce 等方法,所以我們的庫也不能少,我們可以簡單的實現(xiàn)幾個。

map

map 比較簡單,就是將一個 返回值為包裝值的函數(shù) 作用于一個包裝(Wrapped)值的過程, 這里的包裝值可以理解為可以包含其他值的一種結構,例如 Swift 中的數(shù)組,可選類型都是包裝值。它們都有重載的 map, flatMap等函數(shù)。以數(shù)組為例,我們經(jīng)常這樣使用:

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

現(xiàn)在來實現(xiàn)我們的 map 函數(shù):

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 也實現(xiàn)了 map 函數(shù):

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")

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 的不同主要體現(xiàn)在 transform 函數(shù)的返回值不同。map 接受的函數(shù)返回值類型是 U類型,而 flatMap 接受的函數(shù)返回值類型是 U?類型。例如對于一個可選值,可以這樣調(diào)用:

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

我們這里 flatMap 和 Swift 中數(shù)組以及可選型中的 flatMap 保持了一致。

所以我們的 flatMap 應該是這樣定義:flatMap(_ transform: @escaping (Value) -> Signal) -> Signal。

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

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
}

現(xiàn)在我們可以模擬一個網(wǎng)絡請求來測試 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 多行的代碼就實現(xiàn)了一個簡單的響應式編程庫。不過對于一個庫來說,以上的內(nèi)容還遠遠不夠?,F(xiàn)在的 Signal 還不具有原子性,要作為一個實際可用的庫,應該是線程安的。還有我們對 Disposable 的處理也不夠優(yōu)雅,可以模仿 RxSwift 中 DisposeBag 的做法。上面這些問題可以留給讀者自己去思考了。

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

相關文章

  • Swift代碼自定義UIView實現(xiàn)示例

    Swift代碼自定義UIView實現(xiàn)示例

    這篇文章主要為大家介紹了Swift如何自定義UIView的實現(xiàn)示例代碼,有需要的朋友可以借鑒參考下,希望能夠有所幫助祝大家多多進步,早日升職加薪
    2021-10-10
  • Swift中初始化init的方法小結

    Swift中初始化init的方法小結

    Swift有著超級嚴格的初始化方法,不僅強化了designated初始化方法的地位,所有不加修飾的init方法都需要在方法中確保非Optional的實例變量被賦值初始化,下面這篇文章主要給大家介紹了關于Swift中初始化init的相關資料,需要的朋友可以參考下。
    2018-04-04
  • swift學習文檔(筆記)

    swift學習文檔(筆記)

    這篇文章主要介紹了學習swift的筆記,swift最近也比較熱,需要的朋友可以參考下
    2014-09-09
  • Swift心得筆記之運算符

    Swift心得筆記之運算符

    區(qū)別于 C 語言,在 Swift 中你可以對浮點數(shù)進行取余運算(%),Swift 還提供了 C 語言沒有的表達兩數(shù)之間的值的區(qū)間運算符,(a..b和a...b),這方便我們表達一個區(qū)間內(nèi)的數(shù)值。
    2015-04-04
  • swift內(nèi)存管理指針類型使用實例詳解

    swift內(nèi)存管理指針類型使用實例詳解

    這篇文章主要為大家介紹了swift內(nèi)存管理指針類型使用實例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-11-11
  • Swift利用CoreData實現(xiàn)一個通訊錄存儲詳解

    Swift利用CoreData實現(xiàn)一個通訊錄存儲詳解

    這篇文章主要給大家介紹了關于Swift利用CoreData實現(xiàn)一個通訊錄存儲的相關資料,本文是大家學習coreDate的基礎問題,文中通過示例代碼介紹的非常詳細,需要的朋友可以參考借鑒,下面隨著小編來一起學習學習吧。
    2017-12-12
  • SwiftUI圖片縮放、拼圖等處理教程

    SwiftUI圖片縮放、拼圖等處理教程

    SwiftUI是一種使用Swift語言在蘋果設備上構建用戶界面的創(chuàng)新且簡單的方式,下面這篇文章主要給大家介紹了關于SwiftUI圖片縮放、拼圖等處理的相關資料,需要的朋友可以參考下
    2021-08-08
  • 簡陋的swift carthage copy-frameworks 輔助腳本代碼

    簡陋的swift carthage copy-frameworks 輔助腳本代碼

    下面小編就為大家分享一篇簡陋的swift carthage copy-frameworks 輔助腳本代碼,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2018-01-01
  • Swift之UITabBarController 導航控制器的自定義

    Swift之UITabBarController 導航控制器的自定義

    本文給大家介紹swift導航控制器之UITabBarController,本文通過代碼實例給大家講解swift導航控制器,導航控制器類繼承UITabBarController,代碼簡單易懂,需要的朋友可以參考下
    2015-10-10
  • 實例講解Swift中引用類型的ARC自動引用計數(shù)

    實例講解Swift中引用類型的ARC自動引用計數(shù)

    自動引用計數(shù)是在Objective-C中就有的特性,用來輔助管理對象的引用,這里我們就來以實例講解Swift中引用類型的ARC自動引用計數(shù):
    2016-07-07

最新評論

武功县| 大理市| 古蔺县| 大田县| 普洱| 崇仁县| 夏河县| 嘉荫县| 舞阳县| 辰溪县| 沐川县| 密山市| 廊坊市| 隆林| 武定县| 万源市| 寿阳县| 西青区| 改则县| 公安县| 绵竹市| 伊金霍洛旗| 化州市| 博客| 泸水县| 兴海县| 龙州县| 安新县| 临泉县| 石首市| 洞口县| 鄄城县| 亚东县| 石景山区| 凭祥市| 都兰县| 木兰县| 绥中县| 利川市| 祁门县| 张家川|