iOS:實現一套輕量級 MVVM 框架
前言
在客戶端開發項目中,MVC 仍然是主流架構,但是 MVC 也存在十分明顯的弊端:Controller 作爲中介者常常需要負擔大量的業務處理邏輯,所以 MVC 也被戲稱爲 Masive View Controller 架構。緩解這個問題其實有很多途徑,例如:
-
用胖模型分擔 Controller 的模型數據處理工作,提供儘量熟成的業務數據;
-
引入 Manager 或提取 Service 模塊,負責特定業務模塊數據管理;
-
將膨脹的 Controller 業務分攤到多個 Child View Controller;
-
通過 Category 對 Controller 業務邏輯做分文件處理;
此外,MVC 架構模式還普遍存在單元測試推進困難的問題,該問題還是來源於 Controller 負擔過重,由於 Controller 通常需要依賴 View 層和 Model 層模塊,引入 Manager 和 Service 則依賴模塊更加繁多,因此測試 Controller 時通常需要 Mock 很多模塊,打很多的 Stub 以剔除依賴模塊的影響。另外,Controller 的單元測試需要考慮 Controller 複雜的生命週期。
MVVM 可以說是爲了解決 MVC 的以上兩個弊端而存在的。
一、MVVM 架構
View Model 是 MVVM 架構的核心邏輯層。View Model 用於表徵 View 的特性,並通過數據雙向綁定 View Model 和 View,使 View Model 可以驅動 View 刷新界面,同時 View 接收的用戶交互動作也可以更新 View Model 的數據。雙向綁定下的 View Model 和 View 是完全解耦的,因此單元測試工作比起 MVC 架構簡單得多。MVVM 中 Controller 的職能只侷限於 View Model 和 View 的雙向綁定,Controller 邏輯層變得很薄,因此週期問題基本可以忽略不計。
MVVM 之所以能達到以上效果,很大程度上是因爲它將 View 和 View Model 之間的雙向數據流下沉到了框架層。不過這也給 MVVM 帶來了最大的缺點:數據流動控制的實現細節隱藏於核心框架層。近乎黑盒的雙向數據綁定實現,有時會給代碼調試帶來不小的麻煩。另外,隨着核心邏輯大量轉移到 View Model,同樣會帶來 View Model 規模膨脹的問題,另外需要注意,MVVM 通常不能減少代碼量,MVC 仍然是最省代碼的客戶端架構。總之就是,MVVM 值得嘗試,但也沒有想象中神奇。
在強大的 MVVM 框架支持下是可以達到更省代碼的效果的。
二、MVVM 架構實現
iOS 客戶端開發中應用最爲廣泛的 MVVM 框架應該是 Objective-C 語言實現的 RAC 框架和 Swift 語言實現的 RxSwift 框架。RAC 框架給人的第一感覺就是重,在現有的 MVC 架構項目中引入 RAC 框架基本是顛覆性的,需要學習響應式編程、函數式編程、鏈式編程,需要熟悉 RAC 對 UIKit 框架的封裝,更遑論還有一堆基於 RAC 的衍生框架。RxSwift 則還好一點,因爲 RxSwift 的實現本身非常契合 Swift 語言的特點,不過依然是有點重。總之,在傳統 MVC 項目中啓用 RAC 或 RxSwift,絕對不比引入新編程語言的 Flutter、RN 來得簡單。
那麼可不可以用常規的,更輕量的方式來實現 MVVM 框架層邏輯呢?通常第一個想到的就是觀察者模式,恰好 Objective-C 有強大的 KVO 機制。各邏輯分工大致如下:
-
Model:模型層;
-
View:視圖層;
-
View Model:表徵視圖特性;
-
Controller:通過 KVO 設置 View 觀察 View Model 的各個屬性,則 View Model 屬性值變更會驅動 View 刷新。另外,將來自 View 的用戶交互觸發的 Action 或者回調消息轉發到 View Model 中處理;
但是在實現時你會發現,KVO 是通過 Key Path 來配置的,這個 Key Path 有個很致命的弱點,它是字符串!這會有什麼問題呢?想象一下,有一天你要重構代碼,發現某個 View Model 屬性名設置不太合理,你用 Refactor Rename 工具給這個屬性重命名了,此時所有通過 KVO 綁定 View Model 的該屬性的業務邏輯都會出問題,這個時候你只能再手動修改該屬性的數據綁定代碼中對應的 Key Path 字符串。另外,字符串終究是字符串,IDE 不能爲字符串提供編譯時檢查以及提示,所以可以預見開發體驗極差。而且我認爲 KVO 比較適用於上層業務實現,如果將其下沉到框架層則很容易和上層業務邏輯發生衝突。最簡單的例子,如果上層模塊實現observeValueForKeyPath:
時,沒有調用[super observeValueForKeyPath:]
,那底層的數據雙向綁定框架就直接被旁路了。
KVO 方案被 Out 了,還有沒有更好的實現方案呢?這就是下面所要探討的問題。
三、輕量級 MVVM 架構方案
首先要引入 Observable 和 Observer 的概念,注意這裏的 Observable 和 Observer 和 RAC 和 RxSwift 中 Observable 和 Observer 並不一致,甚至有點相反的意味。
-
Observable:可被觀察對象;
-
Observer:觀察者,可以訂閱 Observable,當 Observable 刷新數據時,會觸發 Observer 刷新;
非常直觀地,有了 Observable 和 Observer 就可以打通數據(View Model)驅動界面刷新的單向數據流。那麼從界面的用戶交互 Action 或委託回調到 View Model 的反向數據流呢?其實也是可以通過 Observable 和 Observer 來打通,因爲 Action 的本質其實也是傳遞數據,只要將來自 View 的用戶交互 Action 所傳遞的數據定義爲 Observable,將 View Model 定義爲 Observer 即可以打通反向數據流。總之:
-
View Model 在數據驅動界面刷新數據流中扮演 Observable 的角色,此時 View 扮演 Observer 的角色;
-
View Model 在用戶交互驅動數據更新數據流中扮演 Observer,View 扮演 Observable;
雖然用的是觀察者模式,但是這裏不使用 Notification 和 KVO,而是採用最簡單粗暴的方法,Observable 強持有所有訂閱該 Observable 的 Observer,Observable 值更新時直接觸發所有 Observer 所註冊的操作邏輯。看到這裏可能會有這樣的疑問,這不就循環引用了麼?其實並不是,因爲 Observable 的數據粒度要比 View Model 和 View 低一個等級,也就是說扮演 Observable 角色是指持有若干個 Observable 成員,扮演 Observer 角色是指持有若干個 Observer 成員。這樣一來,View Model 和 View 就不會存在循環引用的問題。
3.1 基本接口
接下來是設計接口。首先按照 Observable 和 Observer 的定義,將其分別定義爲兩套協議:
Observable
協議定義了可被觀察者的基本特徵:
-
Observable
對應一個值value
(公開 API); -
可以通過調用
addObserver:
方法向Observable
添加觀察者(供Observer
調用); -
可以通過調用
removeObserver:
方法從Observable
移除觀察者(供Observer
調用);
@protocol Observer;
/// 可觀察對象,value 成員更新 setter 會驅動註冊的觀察者刷新。註冊觀察者後,觀察者被可觀察對象強持有
@protocol Observable <NSObject>
/// 值
@property(strong, nonatomic, nullable) id value;
/// 添加觀察者
-(void)addObserver:(id<Observer>)observer;
/// 移除觀察者
-(void)removeObserver:(id<Observer>)observer;
@end
Observer
協議定義了觀察者的基本特徵:
-
可以通過訪問
subscribe
屬性訂閱Observable
(公開 API); -
可以通過調用
invoke:
方法觸發刷新(供Observable
調用);
@protocol Observable;
/// 觀察者
@protocol Observer <NSObject>
/// 訂閱可觀察對象
@property(copy, nonatomic, readonly) void (^subscribe)(id<Observable> observable);
/// 觸發值刷新
-(void)invoke:(id)newValue;
@end
基於兩個協議再進一步定義兩個具體類型分別實現這兩套協議。可以發現公開 API 都通過屬性提供,之所以設計爲這種形式,是爲了在開發過程中使用優雅的鏈式調用風格。
/// 可觀察對象
@interface Observable : NSObject<Observable>
/// 構建
@property(copy, nonatomic, class, readonly) Observable *(^create)(id _Nullable defaultValue);
@end
/// 觀察者所註冊的操作
typedef void(^ObserverHandler)(id newValue);
/// 觀察者
@interface Observer : NSObject <Observer>
/// 構建
@property(copy, nonatomic, class, readonly) Observer *(^create)(void);
/// 處理值刷新
@property(copy, nonatomic, readonly) Observer * (^handle)(ObserverHandler);
@end
3.2 基本實現
實現代碼也非常簡單,四個字概括:簡單粗暴。Observable
只管理一個值,而且必須是id
類型。需要注意,Observable
是具有原子性的(不是屬性atomic
那種原子性),也就是說,該框架只能區分Observable
的值 “改變” 或者“不改變”,不存在Observable
的值 “只改變了其中一部分屬性” 這種狀態。
@interface Observable ()
@property(strong, nonatomic) NSMutableArray *observers;
@end
@implementation Observable
@synthesize value = _value;
-(void)setValue:(id)value{
if(![self.value isEqual:value]){
_value = value;
for(id<Observer> observer in self.observers){
[observer invoke:value];
}
}
}
static Observable *(^create)(id) = ^Observable *(id defaultValue){
Observable *observable = [[Observable alloc] init];
observable.value = defaultValue;
return observable;
};
+(Observable *(^)(id))create{
return create;
}
-(void)addObserver:(id<Observer>)observer{
[self.observers addObject:observer];
}
-(void)removeObserver:(id<Observer>)observer{
[self.observers removeObject:observer];
}
-(NSMutableArray *)observers{
if(!_observers){
_observers = [[NSMutableArray alloc] init];
}
return _observers;
}
@end
Observer
實現同樣簡單粗暴。觀察者持有一個 Block,Observer
的invoke:
方法只是簡單調用了該 Block。在Observer
訂閱Observable
時需要指定該 Block 的實現。問題又來了,這不就有循環引用的風險了麼?沒錯,就是有循環引用的風險。但是隻需要在調用subscribe
時,在 Block 實現中使用__weak
和__strong
避免強引用self
即可,就是基本的 Block 防止循環引用的套路。雖然套路簡單,但是需要注意這條規則一定要遵循。
@interface Observer ()
@property(copy, nonatomic) Observer * (^handle)(ObserverHandler);
@property(copy, nonatomic) ObserverHandler handler;
@end
@implementation Observer
@synthesize subscribe = _subscribe;
-(void (^)(id<Observable>))subscribe{
if(!_subscribe){
__weak typeof(self) weakSelf = self;
_subscribe = ^(id<Observable> observable){
__strong typeof(weakSelf) strongSelf = weakSelf;
[observable addObserver:strongSelf];
};
}
return _subscribe;
}
-(void)invoke:(id)newValue{
if(self.handler){
self.handler(newValue);
}
}
static Observer *(^create)(void) = ^Observer *(){
Observer *observer = [[Observer alloc] init];
return observer;
};
+(Observer *(^)(void))create{
return create;
}
-(Observer * (^)(ObserverHandler))handle{
if(!_handle){
__weak typeof(self) weakSelf = self;
_handle = ^Observer *(ObserverHandler handler){
__strong typeof(weakSelf) strongSelf = weakSelf;
strongSelf.handler = handler;
return strongSelf;
};
}
return _handle;
}
@end
3.3 能力擴展
基本實現框架有了,不過僅有Observable
和Observer
的話,貌似只能組織最簡單的數據流拓撲,即從單個Observable
分發到多個Observer
,其實開發過程中還希望具備多個Observable
合成單個Observable
的能力。爲此定義ObservableCombiner
用於實現Observable
合成。
ObservableCombiner
繼承Observable
類型,可以通過調用其combine
屬性合成多個Observable
,同時ObservableCombiner
具備一定Observer
的特徵(但不是遵循Observer
協議),其handle
和Observer
的subscribe
是相同的原理,只是傳參上,前者是NSArray
表示所有合成Observable
的值的數組。當合成的Observable
值更新時,會觸發handle
所註冊的 Block。
/// 合成可觀察對象的觸發策略
typedef NS_ENUM(NSUInteger, CombineStrategy) {
/// 第一次值更新才刷新
CombineStrategyFirst,
/// 所有值更新才刷新
CombineStrategyAll,
/// 任何值更新均刷新
CombineStrategyEvery,
};
/// 合成可觀察對象處理值刷新
typedef _Nullable id (^CombinerHandler)(NSArray *newValues);
/// 合成多個可觀察對象
@interface ObservableCombiner : Observable
/// 安全獲取值
@property(copy, nonatomic, readonly, class) id _Nullable (^safeValue)(NSArray *, NSInteger);
/// 構建
@property(copy, nonatomic, readonly, class) ObservableCombiner *(^create)(CombineStrategy strategy);
/// 合併可觀察對象
@property(copy, nonatomic, readonly) ObservableCombiner * (^combine)(id<Observable> observable);
/// 處理值刷新
@property(copy, nonatomic, readonly) ObservableCombiner * (^handle)(CombinerHandler);
@end
實現代碼直接貼在文章最後,這裏不作詳細介紹了。
後面再嘗試擴展支持從多個
Observable
映射到多個Observable
的能力。
四、輕量級 MVVM 框架 Demo
基於該框架的開發同樣需要以組織數據流的思想作爲指導,框架提供的公開 API 基本是用於組織數據流,核心操作是構建(create)、訂閱(subscribe)和處理(handle)。爲便於描述將 View Model 驅動 View 刷新數據流簡稱爲正向數據流,將來自 View 的界面交互動作驅動 View Model 更新數據流簡稱爲反向數據流,代碼邏輯分佈基本如下:
-
View Model:
-
構建正向數據流的 Observable;
-
構建正向數據流的合成 Observable;
-
處理正向數據流的合成 Observable;
-
構建反向數據流的 Observer;
-
處理反向數據流的 Observer;(交互驅動數據刷新)
-
View:
-
構建反向數據流的 Observable;
-
構建反向數據流的合成 Observable;
-
處理反向數據流的合成 Observable;
-
Controller:
-
訂閱正向數據流的 Observable;
-
訂閱反向數據流的 Observable;
-
構建正向數據流的 Observer;
-
處理正向數據流的 Observer(數據驅動視圖刷新);
看起來很繞,實際上原則可以歸結爲四條:
-
在數據流來源構建 Observable;
-
在 Controller 中訂閱 Observable;
-
在數據流終點處理 Observable;
-
在數據流終點構建 Observer;
注意:雖然在 Controller 中處理正向數據流,但是處理邏輯必須是非常簡單的操作,基本是原子操作,符合操作可以通過在 View 層定義並實現接口,對外向 Controller 提供。其實最合理的佈局是在 View 中構建和處理正向數據流 Observer,不過當 View Model 可以提供非常熟成的數據時,Controller 通過一兩句代碼就可以調起 View 刷新視圖,則沒有必要因此引入實現邏輯過於簡單的新接口。
接下來以登錄業務來演示輕量級 MVVM 框架的使用。業務描述如下:
-
用戶名必須超過 6 個字節不能超過 24 個字節;
-
密碼必須超過 6 個字節不能超過 24 個字節;
-
用戶名和密碼不合法時登錄按鈕不可點擊;
-
用戶名和密碼不合法時給出相應提示,優先顯示用戶名的錯誤提示;
首先定義 View Model:
@interface LoginViewModel : NSObject
//MARK: 數據驅動UI刷新
@property(strong, nonatomic) Observable *username;
@property(strong, nonatomic) Observable *password;
@property(strong, nonatomic) Observable *instruction;
@property(strong, nonatomic) ObservableCombiner *usernameValid;
@property(strong, nonatomic) ObservableCombiner *passwordValid;
@property(strong, nonatomic) ObservableCombiner *loginEnabled;
//MARK: 用戶交互動作訂閱
@property(strong, nonatomic) Observer *usernameDidChange;
@property(strong, nonatomic) Observer *passwordDidChange;
@property(strong, nonatomic) Observer *loginTouched;
@end
其次定義 View:
@interface LoginView : UIView
@property(strong, nonatomic) UITextField *usernameTextField;
@property(strong, nonatomic) UITextField *passwordTextField;
@property(strong, nonatomic) UILabel *instructionLabel;
@property(strong, nonatomic) UIButton *loginButton;
@property(strong, nonatomic) Observable *usernameDidChange;
@property(strong, nonatomic) Observable *passwordDidChange;
@property(strong, nonatomic) Observable *loginButtonTouched;
@end
4.1 數據流組織
正向數據流組織及反向數據流處理是 View Model 的核心邏輯。組織正向數據流的代碼如下,通過代碼可以非常直觀地閱讀出以下關鍵信息,從而生成非常清晰的正向數據流拓撲:
-
usernameValid
依賴於username
和passwordValid
的值; -
passwordValid
依賴於password
和usernameValid
的值; -
loginEnabled
依賴於usernameValid
和passwordValid
的值;
-(void)doDataWeaving{
self.usernameValid
.combine(self.username)
.combine(self.passwordValid)
.handle(^id _Nullable(NSArray * _Nonnull newValues) {
NSString *username = ObservableCombiner.safeValue(newValues, 0);
if(!username.length){
self.instruction.value = @"*用戶名不能爲空";
return @(NO);
}
if(username.length < 6){
self.instruction.value = @"*用戶名必須超過6個字符";
return @(NO);
}
if(username.length > 24){
self.instruction.value = @"*用戶名不能超過24個字符";
return @(NO);
}
BOOL passwordValid = [ObservableCombiner.safeValue(newValues, 1) boolValue];
if(passwordValid){
self.instruction.value = @"";
}
return @(YES);
});
self.passwordValid
.combine(self.usernameValid)
.combine(self.password)
.handle(^id _Nullable(NSArray * _Nonnull newValues) {
NSString *password = ObservableCombiner.safeValue(newValues, 1);
BOOL usernameValid = [ObservableCombiner.safeValue(newValues, 0) boolValue];
BOOL passwordValid;
NSString *passwordInstruction;
if(!password.length){
passwordInstruction = @"*密碼不能爲空";
passwordValid = NO;
}else if(password.length < 6){
passwordInstruction = @"*密碼必須超過6個字符";
passwordValid = NO;
}else if(password.length > 24){
passwordInstruction = @"*密碼不能超過24個字符";
passwordValid = NO;
}else{
passwordInstruction = @"";
passwordValid = YES;
}
if(usernameValid){
self.instruction.value = passwordInstruction;
}
return @(passwordValid);
});
self.loginEnabled
.combine(self.usernameValid)
.combine(self.passwordValid)
.handle(^id _Nullable(NSArray * _Nonnull newValues) {
BOOL usernameValid = [ObservableCombiner.safeValue(newValues, 0) boolValue];
BOOL passworkValid = [ObservableCombiner.safeValue(newValues, 1) boolValue];
return @(usernameValid && passworkValid);
});
}
處理反向數據流的代碼如下,代碼比較簡單不作贅述:
-(void)doActionHandlings{
WS
// 用戶交互處理
self.usernameDidChange = Observer.create().handle(^(id _Nonnull newValue) {
SS
self.username.value = newValue;
});
self.passwordDidChange = Observer.create().handle(^(id _Nonnull newValue) {
SS
self.password.value = newValue;
});
self.loginTouched = Observer.create().handle(^(id _Nonnull newValue) {
self.instruction.value = [NSString stringWithFormat:@"登錄成功(*^▽^*)"];
});
}
4.2 訂閱的實現
訂閱是 Controller 絕對的核心邏輯,包括正向數據流和反向數據流訂閱。正向數據流訂閱的實現代碼如下,包括:
-
正向數據流 Observable 訂閱;
-
正向數據流 Observer 構建及處理;
/// 數據驅動UI刷新
-(void)doDataBindings{
WS
Observer.create().handle(^(id _Nonnull newValue) {
SS
self.loginView.usernameTextField.text = newValue;
}).subscribe(self.loginViewModel.username);
Observer.create().handle(^(id _Nonnull newValue) {
SS
self.loginView.passwordTextField.text = newValue;
}).subscribe(self.loginViewModel.password);
Observer.create().handle(^(id _Nonnull newValue) {
SS
self.loginView.instructionLabel.text = newValue;
}).subscribe(self.loginViewModel.instruction);
Observer.create().handle(^(id _Nonnull newValue) {
SS
self.loginView.usernameTextField.backgroundColor = [newValue boolValue] ? [UIColor whiteColor] : LightRed;
}).subscribe(self.loginViewModel.usernameValid);
Observer.create().handle(^(id _Nonnull newValue) {
SS
self.loginView.passwordTextField.backgroundColor = [newValue boolValue] ? [UIColor whiteColor] : LightRed;
}).subscribe(self.loginViewModel.passwordValid);
Observer.create().handle(^(id _Nonnull newValue) {
SS
self.loginView.loginButton.enabled = [newValue boolValue];
self.loginView.loginButton.backgroundColor = [newValue boolValue] ? ThemeColor : LightGray;
[self.loginView.loginButton setTitleColor:[newValue boolValue] ? [UIColor whiteColor] : [UIColor darkGrayColor] forState:UIControlStateNormal];
}).subscribe(self.loginViewModel.loginEnabled);
}
反向數據流訂閱的實現代碼如下;
/// 用戶交互動作訂閱
-(void)doActionBindings{
self.loginViewModel.usernameDidChange.subscribe(self.loginView.usernameDidChange);
self.loginViewModel.passwordDidChange.subscribe(self.loginView.passwordDidChange);
self.loginViewModel.loginTouched.subscribe(self.loginView.loginButtonTouched);
}
從代碼上看,不難發現,該框架是一點都不省代碼,同等的 MVC 架構實現比上面的實現在代碼空間行數上少 50% 左右,但是基於該框架實現的業務代碼數據流非常清晰,代碼邏輯分佈更加均勻,View Model 處理純粹的業務邏輯,也非常契合引入 Manager 和 Service 模塊分流數據管理負擔的優化方式。
五、總結
雖然只定義了Observable
、Observer
、ObservableCombiner
,但是它已經具備了構建 MVVM 架構的基本能力了。首先,肉眼可見的,它足夠輕量。其次,不存在前文所述 KVO 方案的缺陷。最後,它麻雀雖小,其實五臟俱全,正確使用該框架可以獲得漂亮工整的代碼邏輯結構。在後面 Demo 開發過程中實際應用該框架時,感覺開發體驗總體還是不錯的。
當然本方案還是有非常明顯的缺陷的,例如:
-
Observable
值類型只支持id
類型; -
組織數據流拓撲結構支持還存在不小的缺失;
-
存在冗餘的數據刷新次數;
-
實現代碼增幅十分明顯,本文 Demo 相對 MVC 架構同等實現,代碼行數增加了 50%(空間行數);
-
調試過程中數據流向跟蹤困難;
-
訂閱時需要避免 Block 循環引用問題;
總之,本文的輕量級 MVVM 框架方案可以用來體驗 iOS 客戶端開發中的 MVVM 架構模式的應用,或者理解 MVVM 架構的原理。本方案優點和缺點同等明顯,由於目前缺乏完備的測試,以及對複雜業務場景的實踐案例支撐,暫時不打算直接應用到開發項目中。
附錄
附錄一:Combiner 實現
// ObservableCombiner.m
@interface ObservableCombiner ()
@property(strong, nonatomic) NSMutableArray *observables;
@property(assign, nonatomic) CombineStrategy strategy;
@property(assign, nonatomic) NSUInteger accessFlags;
@property(copy, nonatomic) ObservableCombiner * (^combine)(id<Observable> observable);
@property(copy, nonatomic) ObservableCombiner * (^handle)(CombinerHandler);
@property(copy, nonatomic) CombinerHandler handler;
@end
@implementation ObservableCombiner
static ObservableCombiner *(^create)(CombineStrategy strategy) = ^ObservableCombiner *(CombineStrategy strategy){
ObservableCombiner *combiner = [[ObservableCombiner alloc] init];
combiner.strategy = strategy;
return combiner;
};
+(ObservableCombiner *(^)(CombineStrategy))create{
return create;
}
-(ObservableCombiner * (^)(id<Observable>))combine{
if(!_combine){
__weak typeof(self) weakSelf = self;
_combine = ^ObservableCombiner * (id<Observable> observable){
__strong typeof(weakSelf) strongSelf = weakSelf;
NSInteger index = strongSelf.observables.count;
id<Observer> observer = Observer.create().handle(^(id _Nonnull newValue) {
__strong typeof(weakSelf) strongSelf = weakSelf;
[strongSelf handleNewValue:newValue index:index];
});
[observable addObserver:observer];
[strongSelf.observables addObject:observable];
return strongSelf;
};
}
return _combine;
}
-(ObservableCombiner * (^)(CombinerHandler))handle{
if(!_handle){
__weak typeof(self) weakSelf = self;
_handle = ^ObservableCombiner *(CombinerHandler handler){
__strong typeof(weakSelf) strongSelf = weakSelf;
strongSelf.handler = handler;
return strongSelf;
};
}
return _handle;
}
-(NSMutableArray *)observables{
if(!_observables){
_observables = [[NSMutableArray alloc] init];
}
return _observables;
}
-(void)handleNewValue:(id)newValue index:(NSInteger)index{
// 根據不同的策略觸發完成事件
BOOL isFirst = !self.accessFlags;
self.accessFlags |= (1 << index);
switch (self.strategy) {
case CombineStrategyFirst:{
if(isFirst){
self.value = self.handler([self getAllValues]);
}
}break;
case CombineStrategyEvery:{
self.value = self.handler([self getAllValues]);
}break;
case CombineStrategyAll:{
NSUInteger allFlags = pow(2, self.observables.count) - 1;
BOOL isAll = (allFlags & self.accessFlags) == allFlags;
if(isAll){
self.value = self.handler([self getAllValues]);
}
}break;
default:
break;
}
}
-(NSArray *)getAllValues{
NSMutableArray *result = [[NSMutableArray alloc] init];
for(id<Observable> observable in self.observables){
[result addObject:observable.value ?: [NSNull null]];
}
return result;
}
static id _Nullable (^safeValue)(NSArray *, NSInteger) = ^id (NSArray *values, NSInteger index){
return values[index] == [NSNull null] ? nil : values[index];
};
+(id _Nullable (^)(NSArray * _Nonnull, NSInteger))safeValue{
return safeValue;
}
@end
附錄二:源碼
[1] 源碼及 Demo 地址: https://github.com/Luminixus/DataDrivenMVVM.git
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/KXAD7XaP23ymoZihzNIt8A