MVC一直以來是代碼組織架構中蘋果公司所推崇的開發模式,但因爲工程類文件的日益增多,MVC中C層(Controller層)的日益臃腫,產品需求複雜化,迭代速度愈來愈快,老架構設計已經漸漸跟不上高強度的組件化團隊化開發了。最近一直在尋求一種開發模式,能讓多個團隊成員能夠同時開發且邏輯清晰。期間閱讀了不少文章,好比VIPER架構、UBer公司未開源的Riblets架構、MVVM架構等,最終決定本身針對MVVM進行一次架構改造,並加入VIPER的特色。其中MVVM的ViewModel的輕實現,當下被列爲攻堅環節。git
MVVM的ViewModel中採用KVO的觀察者模式監聽,調用ViewController來進行整個架構的解耦設計。在Objective-C當中得益於強大的Runtime機制能夠實現對任意類型的觀察者監聽。雖然Objective-C中能夠任意定義KVO,可是經歷過大項目的朋友必定首先會想到Objective-C中的KVO在使用的輕便型上差強人意,須要addObserver和removeObserver,且若是Context上下文弄錯了,會有必定的崩潰風險,這是須要深入瞭解Objective-C的釋放避免指針的循環引用等。github
Swift做爲一個靜態編譯型語言,它摒棄了Objective-C中的Runtime機制。想要開啓動態Property須要再Swift的Property前面增長聲明:dynamic,且使用dynamic必須是基於NSObject基類所構造的類型,這樣作必然會喪失對Swift原始數據類型的支持,可見其是很差的。並且預計沒有多少朋友記得給變量打上dynamic的標記吧,起碼我不會編程
很慶幸的是Swift語言在本身的Property中增長了getter/ setter的屬性觀察器,並對setter的屬性觀察器提供了willSet / didSet的兩個觀察器來詳細監聽值的變化。這讓咱們看到Swift自己是汲取了Objective-C在Runtime中創造的經驗和靈感,並將觀察者模式輕量化,以至關優雅的方式去表示一個值的變化過程。swift
class valueDemo { var value:String = "" { willSet { print("newValue:", newValue) } didSet { print("done:", value) } } }
但是咱們在開發中不只僅是這樣的簡單環境,咱們須要針對MVVM中ViewModel開放一個被觀察者鏈接給ViewController,二者產生聯動。此時有人想到:"我提供一個閉包(block)設置給didSet就行了呀"。確實你能夠這樣作。爲每個Property提供一個block雖然可行,但沒有重用好這一機制是則會讓代碼變得重複。那咱們就要尋找一個好一點的方法來能讓Property變成一個被觀察者,當它發生變動的時候,觸發一批block回調。api
ReactiveCocoa和RxSwift的第三方庫來實現是能夠很好地實現觀察者模式(筆者更喜歡後者RxSwift的書寫風格)。確實,如今MVVM中採用RxSwift解耦做爲中間件確實是產品開發潮流,這就像某種服裝搭配趨勢同樣的流行。那問題隨之而來,採用ReactiveCocoa和RxSwift都哪些共同缺點呢?咱們開發實戰的時候確定會遇到下面的問題:數組
基於以上幾點缺點,我在這裏不贊同採用這樣的第三方組件的開發方式開發,雖然它們很酷炫、顯得高大上!xcode
那難道沒有一個又輕又容易維護的觀察者模式嗎?答案是有的!
那咱們就從零開始一步步實現一個基於Swift 3~4的低調奢華有內涵的觀察者模式(題外話因爲我所書寫的日期是2017-6-6,正好是Swift 4發佈當日,個人工程文件又一次被Swift4的升級所摧毀,被摧毀的是第三方庫,那我仍是本身造一個輪子吧!)閉包
先來描述一下基本原理:架構
先來看一下基礎代碼:app
// 須要持有一批blocks,則必須建立一個類做爲空間 class Observable<T> { typealias ObservableBlock = (T) -> () private var blocks: [ObserverBlock] = [] // 持有blocks init(_ t:T) { self.value = t } // 初始化value var value:T { didSet { // 實現didSet來遍歷block,觸發回調 for block in blocks { block(self.value) } } } // 訂閱 func subscribe(block:@escaping ObserverBlock) { blocks.append(block) } }
run exmple:
let example = Observable<String>("") example.subscribe { (newValue:String) in print("newValue:", newValue) } example.value = "a" example.value = "b"
代碼的運行結果:
newValue: a newValue: b
看到運行結果,很不錯!基於簡單blocks持有,基於didSet就能夠完成對於一個變量設置的變動監聽。
仔細打量了代碼,中間缺乏幾個能力:
第一步咱們先來加入高級運算符重載,片斷代碼:
infix operator <-: ObservableChange precedencegroup ObservableChange { associativity: left // 表示左結合 } public func <- <T> (left: Observable<T>, right: T) { left.value = right }
完整代碼:[純block,可自動釋放內存]
// 高級運算符重載必須聲明在final頂級訪問級別的類中 public final class Observable<T> { typealias ObserverBlock = (T) -> () private var blocks: Array<ObserverBlock> = Array() init(_ t:T) { self.value = t } var value:T { didSet { for block in blocks { block(self.value) } } } func subscribe(block:@escaping ObserverBlock) { blocks.append(block) } deinit { print("Observable", #function) } } /* 定義 <- 運算符 運算符定義必須放在文件級別當中 */ infix operator <-: ObservableChange precedencegroup ObservableChange { associativity: left // 表示左結合 } public func <- <T> (left: Observable<T>, right: T) { left.value = right }
run exmple :
let example = Observable<String>("") example.subscribe { (newValue:String) in print("newValue:", newValue) } example.value = "a" example.value = "b" example <- "a"
代碼的運行結果:
newValue: a newValue: b newValue: a
重載看上去還不錯,很精簡!那繼續完善,填補後續的功能
第二步添加unSubscribe方法
起初我想直接經過block閉包的相等性檢查,經過block閉包相等,來移除blocks中的指定閉包,可是失敗了。好比代碼:
public final class Observable<T> { typealias ObserverBlock = (T) -> () private var blocks: Array<ObserverBlock> = Array() init(_ t:T) { self.value = t } var value:T { didSet { for block in blocks { block(self.value) } } } func subscribe(block:@escaping ObserverBlock) { blocks.append(block) } // 移除訂閱 func unSubscript(block:@escaping ObservableBlock) { var blocksFiltered = blocks.filter { (blockInArray:ObservableBlock) -> Bool in return blockInArray !== block // !!!!!!!沒法編譯,編譯報錯!!!!!!! //報錯信息: Cannot check reference equality of functions;operands here have type '(T)->()' and '(T)->()' } self.blocks = blocksFiltered } }
看到//報錯信息: Cannot check reference equality of functions;operands here have type '(T)->()' and '(T)->()'
發現Swift中是不容許將兩個閉包進行的比較的。雖然遺留的C API中是有unsafeBitCast能夠對兩個閉包進行比較,但我仍是放棄這樣的寫法。
unsafeBitCast 相關使用:https://stackoverflow.com/questions/24111984/how-do-you-test-functions-and-closures-for-equality
那既然block沒法比較相等,就只能講上下文與blocks進行綁定關係,來實現訂閱和刪除訂閱。
// 定義高級運算符重載,必須爲final訪問權限的聲明 public final class Observable<T> { typealias ObserverBlock = (_ oldValue:T, _ newValue:T) -> () // 訂閱block,增長old和new的傳值 typealias ObserverEntry = (observer: AnyObject, block: ObserverBlock) // 觀察者元組 private var observers: [ObserverEntry] // 觀察者Array init(_ value:T) { self.value = value observers = [] } var value:T { didSet { observers.forEach { (entry: ObserverEntry) in let (_, block) = entry block(oldValue, value) } } } // 訂閱,建立觀察者元組 func subscribe(observer:AnyObject, block:@escaping ObserverBlock) { observers.append(ObserverEntry(observer:observer, block:block)) } // 解除訂閱,根據元組中的觀察者移除 func unSubscribe(observer:AnyObject) { let filtered = observers.filter { (entry: ObserverEntry) in let (owner, _) = entry return owner !== observer } observers = filtered } } infix operator <-: ObservableChange precedencegroup ObservableChange { associativity: left // 表示左結合 } // 運算符重載 public func <- <T> (left: Observable<T>, right: T) { left.value = right }
run example:
let example = Observable<String>("") example.subscribe(observer: self) { (oldValue:String, newValue:String) in print("oldValue:", oldValue, "newValue:", newValue) } example.value = "a" example.value = "b" example <- "a" example.unSubscribe(observer: self) example <- "c" // 取消訂閱,則不會看到"c"的打印
代碼的運行結果:
oldValue: newValue: a oldValue: a newValue: b oldValue: b newValue: a // 這裏沒有看到「c」
好了,通過細細打磨的Observable已經初步具有了觀察者能力了,而且能夠輕巧的應用於變量的觀察
所有代碼:
https://github.com/slazyk/Observable-Swift
我在編寫期間試用了google的一個開發者開發的Observable-Swift的,但這個只針對於Swift 3且功能略顯複雜,最後放棄。
不過本觀察者訂閱模式和其餘的第三方組件其實都有弊端:
而內存循環應用,須要將被保存在entry當中的Observer在必要的時候unSubscribe掉才能夠解決循環引用的問題。因此當以爲Observer沒有必要的話,仍是直接使用只有一個Block的版本吧![純block,可自動釋放內存]