本文原創(chuàng) 作者:叢瑜帥 如需轉(zhuǎn)載請(qǐng)注明 (2017-06-06)
起因:
MVC一直以來是代碼組織架構(gòu)中蘋果公司所推崇的開發(fā)模式,但由于工程類文件的日益增多,MVC中C層(Controller層)的日益臃腫,產(chǎn)品需求復(fù)雜化,迭代速度越來越快,老架構(gòu)設(shè)計(jì)已經(jīng)漸漸跟不上高強(qiáng)度的組件化團(tuán)隊(duì)化開發(fā)了。最近一直在尋求一種開發(fā)模式,能讓多個(gè)團(tuán)隊(duì)成員可以同時(shí)開發(fā)且邏輯清晰。期間閱讀了很多文章,比如VIPER架構(gòu)、UBer公司未開源的Riblets架構(gòu)、MVVM架構(gòu)等,最終決定自己針對(duì)MVVM進(jìn)行一次架構(gòu)改造,并加入VIPER的特點(diǎn)。其中MVVM的ViewModel的輕實(shí)現(xiàn),當(dāng)下被列為攻堅(jiān)環(huán)節(jié)。

MVVM的ViewModel中采用KVO的觀察者模式監(jiān)聽,調(diào)用ViewController來進(jìn)行整個(gè)架構(gòu)的解耦設(shè)計(jì)。在Objective-C當(dāng)中得益于強(qiáng)大的Runtime機(jī)制可以實(shí)現(xiàn)對(duì)任意類型的觀察者監(jiān)聽。雖然Objective-C中可以任意定義KVO,但是經(jīng)歷過大項(xiàng)目的朋友一定首先會(huì)想到Objective-C中的KVO在使用的輕便型上差強(qiáng)人意,需要addObserver和removeObserver,且如果Context上下文弄錯(cuò)了,會(huì)有一定的崩潰風(fēng)險(xiǎn),這是需要深刻了解Objective-C的釋放避免指針的循環(huán)引用等。
Swift作為一個(gè)靜態(tài)編譯型語言,它摒棄了Objective-C中的Runtime機(jī)制。想要開啟動(dòng)態(tài)Property需要再Swift的Property前面增加聲明:dynamic,且使用dynamic必須是基于NSObject基類所構(gòu)造的類型,這樣做必然會(huì)喪失對(duì)Swift原始數(shù)據(jù)類型的支持,可見其是不好的。而且預(yù)計(jì)沒有多少朋友記得給變量打上dynamic的標(biāo)記吧,起碼我不會(huì)
很慶幸的是Swift語言在自己的Property中增加了getter/ setter的屬性觀察器,并對(duì)setter的屬性觀察器提供了willSet / didSet的兩個(gè)觀察器來詳細(xì)監(jiān)聽值的變化。這讓我們看到Swift本身是汲取了Objective-C在Runtime中創(chuàng)造的經(jīng)驗(yàn)和靈感,并將觀察者模式輕量化,以相當(dāng)優(yōu)雅的方式去表示一個(gè)值的變化過程。
class valueDemo {
var value:String = "" {
willSet {
print("newValue:", newValue)
}
didSet {
print("done:", value)
}
}
}
可是我們?cè)陂_發(fā)中不僅僅是這樣的簡(jiǎn)單環(huán)境,我們需要針對(duì)MVVM中ViewModel開放一個(gè)被觀察者連接給ViewController,兩者產(chǎn)生聯(lián)動(dòng)。此時(shí)有人想到:"我提供一個(gè)閉包(block)設(shè)置給didSet就好了呀"。確實(shí)你可以這樣做。為每一個(gè)Property提供一個(gè)block雖然可行,但沒有重用好這一機(jī)制是則會(huì)讓代碼變得重復(fù)。那我們就要尋找一個(gè)好一點(diǎn)的方法來能讓Property變成一個(gè)被觀察者,當(dāng)它發(fā)生變更的時(shí)候,觸發(fā)一批block回調(diào)。
分析第三方:
ReactiveCocoa和RxSwift的第三方庫來實(shí)現(xiàn)是可以很好地實(shí)現(xiàn)觀察者模式(筆者更喜歡后者RxSwift的書寫風(fēng)格)。確實(shí),現(xiàn)在MVVM中采用RxSwift解耦作為中間件確實(shí)是產(chǎn)品開發(fā)潮流,這就像某種服裝搭配趨勢(shì)一樣的流行。那問題隨之而來,采用ReactiveCocoa和RxSwift都哪些共同缺點(diǎn)呢?我們開發(fā)實(shí)戰(zhàn)的時(shí)候肯定會(huì)遇到下面的問題:
- 訂閱和分發(fā)導(dǎo)致它本身的執(zhí)行效率低,會(huì)有大量的觸發(fā)棧和循環(huán)去進(jìn)行訂閱消息的分發(fā),遍歷逐個(gè)投遞。
- Swift本身的語法導(dǎo)致從Swift v.2 -> v.3 -> v.4的語法升級(jí)受制于蘋果的語言規(guī)則。Swift語言開發(fā)者的開發(fā)理念是快速激進(jìn)式的開發(fā)(我給它定名為:語法摧毀),雖然xcode提供了自動(dòng)化轉(zhuǎn)換語法功能,但難免會(huì)有轉(zhuǎn)換錯(cuò)誤和手動(dòng)修改的情況。這樣對(duì)于我們程序本身是非常不穩(wěn)定的變化,導(dǎo)致我們出現(xiàn)重寫程序組件的問題,甚至摧毀式的無法編譯
- ReactiveCocoa和RxSwift的開發(fā)成本比較高,語法體系“奇特”(碎片化的代碼,打散業(yè)務(wù)邏輯,由第三方庫限定語法編寫方式),導(dǎo)致團(tuán)隊(duì)間在合作時(shí)邏輯代碼理解難度加大。團(tuán)隊(duì)成員間的代碼溝通變慢。如果團(tuán)隊(duì)加入新人,學(xué)習(xí)成本則會(huì)提高。
- 庫文件升級(jí)緩慢,受制于他人,如果停止更新,可能你的產(chǎn)品就要趕緊尋找其他第三方庫來進(jìn)行重構(gòu)。
基于以上幾點(diǎn)缺點(diǎn),我在這里不贊同采用這樣的第三方組件的開發(fā)方式開發(fā),雖然它們很酷炫、顯得高大上!
全新創(chuàng)建:
那難道沒有一個(gè)又輕又容易維護(hù)的觀察者模式嗎?答案是有的!
那我們就從零開始一步步實(shí)現(xiàn)一個(gè)基于Swift 3~4的低調(diào)奢華有內(nèi)涵的觀察者模式(題外話由于我所書寫的日期是2017-6-6,正好是Swift 4發(fā)布當(dāng)日,我的工程文件又一次被Swift4的升級(jí)所摧毀,被摧毀的是第三方庫,那我還是自己造一個(gè)輪子吧!)
先來描述一下基本原理:
- 實(shí)現(xiàn)一個(gè)用于產(chǎn)生被觀察者的自定義泛型類:Observable<T>
- Observable自身提供blocks的閉包數(shù)組存放訂閱者的閉包
- 基于Observable中的value的setter方法,手動(dòng)調(diào)用每個(gè)閉包
先來看一下基礎(chǔ)代碼:
// 需要持有一批blocks,則必須創(chuàng)建一個(gè)類作為空間
class Observable<T> {
typealias ObservableBlock = (T) -> ()
private var blocks: [ObserverBlock] = [] // 持有blocks
init(_ t:T) { self.value = t } // 初始化value
var value:T {
didSet {
// 實(shí)現(xiàn)didSet來遍歷block,觸發(fā)回調(diào)
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"
代碼的運(yùn)行結(jié)果:
newValue: a
newValue: b
看到運(yùn)行結(jié)果,很不錯(cuò)!基于簡(jiǎn)單blocks持有,基于didSet就可以完成對(duì)于一個(gè)變量設(shè)置的變更監(jiān)聽。
繼續(xù)完善
仔細(xì)打量了代碼,中間缺少幾個(gè)能力:
- 如何將example.value = "a"的寫法,將開發(fā)者的敲擊鍵盤所消耗的卡路里降到最低呢。賦值形式換為:example <- "a"
這里想到了Swift的《高級(jí)運(yùn)算符重載》:【https://www.cnswift.org/advanced-operators#spl-17】 - 缺少刪除訂閱者block能力。這個(gè)能力需要在訂閱時(shí)將訂閱者傳遞給Observable加以持有,并提供unSubscribe方法
第一步我們先來加入高級(jí)運(yùn)算符重載,片段代碼:
infix operator <-: ObservableChange
precedencegroup ObservableChange {
associativity: left // 表示左結(jié)合
}
public func <- <T> (left: Observable<T>, right: T) {
left.value = right
}
完整代碼:<a name="block_observable">[純block,可自動(dòng)釋放內(nèi)存]</a>
// 高級(jí)運(yùn)算符重載必須聲明在final頂級(jí)訪問級(jí)別的類中
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)
}
}
/*
定義 <- 運(yùn)算符
運(yùn)算符定義必須放在文件級(jí)別當(dāng)中
*/
infix operator <-: ObservableChange
precedencegroup ObservableChange {
associativity: left // 表示左結(jié)合
}
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"
代碼的運(yùn)行結(jié)果:
newValue: a
newValue: b
newValue: a
重載看上去還不錯(cuò),很精簡(jiǎn)!那繼續(xù)完善,填補(bǔ)后續(xù)的功能
第二步添加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 // !!!!!!!無法編譯,編譯報(bào)錯(cuò)!!!!!!!
//報(bào)錯(cuò)信息: Cannot check reference equality of functions;operands here have type '(T)->()' and '(T)->()'
}
self.blocks = blocksFiltered
}
}
看到//報(bào)錯(cuò)信息: Cannot check reference equality of functions;operands here have type '(T)->()' and '(T)->()'
發(fā)現(xiàn)Swift中是不允許將兩個(gè)閉包進(jìn)行的比較的。雖然遺留的C API中是有unsafeBitCast可以對(duì)兩個(gè)閉包進(jìn)行比較,但我還是放棄這樣的寫法。
unsafeBitCast 相關(guān)使用:https://stackoverflow.com/questions/24111984/how-do-you-test-functions-and-closures-for-equality
那既然block無法比較相等,就只能講上下文與blocks進(jìn)行綁定關(guān)系,來實(shí)現(xiàn)訂閱和刪除訂閱。
// 定義高級(jí)運(yùn)算符重載,必須為final訪問權(quán)限的聲明
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)
}
}
}
// 訂閱,創(chuàng)建觀察者元組
func subscribe(observer:AnyObject, block:@escaping ObserverBlock) {
observers.append(ObserverEntry(observer:observer, block:block))
}
// 解除訂閱,根據(jù)元組中的觀察者移除
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 // 表示左結(jié)合
}
// 運(yùn)算符重載
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" // 取消訂閱,則不會(huì)看到"c"的打印
代碼的運(yùn)行結(jié)果:
oldValue: newValue: a
oldValue: a newValue: b
oldValue: b newValue: a
// 這里沒有看到“c”
已知弊端:
不過本觀察者訂閱模式和其他的第三方組件其實(shí)都有弊端:
- 就是插入式編程,
- 內(nèi)存循環(huán)應(yīng)用
插入式編程就是會(huì)將原有的代碼的變量類型破壞,從而讓類型都趨向于Observable<T>數(shù)據(jù)類型,這樣喜歡純正變量監(jiān)聽的話,當(dāng)下除了willSet和didSet,尚未發(fā)現(xiàn)其他更優(yōu)雅的方法!
*** 而內(nèi)存循環(huán)應(yīng)用,需要將被保存在entry當(dāng)中的Observer在必要的時(shí)候unSubscribe掉才可以解決循環(huán)引用的問題。
2017-06-12后續(xù)
經(jīng)過測(cè)試我采用了自動(dòng)釋放和手動(dòng)釋放兩個(gè)方式編寫Observable源碼。而上面的代碼中,我將subscribe:Observer修改為block與ObserverName綁定的形式,來解決內(nèi)存循環(huán)引用的問題。
進(jìn)一步修改代碼我們來看一下:
// final class for operator <-
// 高級(jí)運(yùn)算符重載必須聲明在final頂級(jí)訪問級(jí)別的類中
public final class Observable<T> {
typealias ObserverBlock = (_ oldValue: T, _ newValue: T) -> ()
typealias ObserversEntry = (block: ObserverBlock, observerName:String?)
private var observers: Array<ObserversEntry>
init(_ value: T) {
self.value = value
observers = []
}
var value: T {
didSet {
observers.forEach { (entry: ObserversEntry) in
let (block, _) = entry
block(oldValue, value)
}
}
}
func subscribe(block: @escaping ObserverBlock) -> Self {
let entry: ObserversEntry = (block: block, nil)
observers.append(entry)
return self
}
// set ObserverName for unsubscribe
func addObserverName(_ observerName: String) {
if observers.count > 0 {
observers[observers.count-1].observerName = observerName
}
}
// remove subscribe with ObserverName
func unSubscribe(_ observerName: String) {
let filtered = observers.filter { entry in
let (_, observerNameSaved) = entry
if (observerNameSaved != nil) {
return observerNameSaved != observerName
} else {
return true
}
}
observers = filtered
}
}
/*
定義 <- 運(yùn)算符
運(yùn)算符定義必須放在文件級(jí)別當(dāng)中
*/
infix operator <-: ObservableChange
precedencegroup ObservableChange {
associativity: left // 表示左結(jié)合
}
public func <- <T> (left: Observable<T>, right: T) {
left.value = right
}
看到上方最新的代碼,我們可以觀察到添加了一個(gè)addObserverName(_ observerName:)用于給訂閱block注冊(cè)關(guān)鍵字,這樣就可以將一批訂閱者注冊(cè)并取消訂閱。且沒有內(nèi)存引用問題。思路我借鑒了RXSwift的調(diào)用時(shí)機(jī):addDisposeBag(disposebag:)
那么釋放的使用方法如下:
let example = Observable<String>("")
example.subscribe { (old:String, new:String) in
print("oldValue:", old, "newValue:", new)
}.addObserverName("TheExampleName")
example <- "a"
example.unSubscribe("TheExampleName")
好了,經(jīng)過細(xì)細(xì)打磨的Observable已經(jīng)初步具備了觀察者能力了,并且可以輕巧的應(yīng)用于變量的觀察
全部代碼:
https://github.com/maxcong/Observable-Block-Swift
我在編寫期間試用了google的一個(gè)開發(fā)者開發(fā)的Observable-Swift的,
但這個(gè)只針對(duì)于Swift 3,功能略復(fù)雜了,最后放棄
此文拋磚引玉,希望看到的開發(fā)者如果有優(yōu)雅的方法可以在文章后面留言。深表感謝!
本文原創(chuàng) 作者:叢瑜帥 如需轉(zhuǎn)載請(qǐng)注明