全域性資料同步(三)終極方案
在全域性資料同步系列文章中(一)(二)分別解決了model和view的全域性同步,但是依然有一些問題,所以在這裡給一個終極解決方案DDKeyPathChannel。
重新來說明下解決的問題
由於各種原因,目前有兩個表示同一種型別的model。
@interface UserModel1 : NSObject
@property (strong, nonatomic) NSString *id;
@property (strong, nonatomic) NSString *name;
@property (assign, nonatomic) NSInteger age;
@end
@interface UserModel2 : NSObject
@property (strong, nonatomic) NSString *id;
@property (strong, nonatomic) NSString *nickName;
@property (assign, nonatomic) NSInteger age;
@end
現在需要其中一個屬性修改了,或者第三方要求更新屬性,如何更好的同步各個不同model之間的屬性呢?
另一個問題,又如何把這個狀態更新到UI上呢?
以前方案的問題
首先,來看看之前解決方案的幾個問題。
- 所有基類都需要實現特定介面協議。這對於model來說會比較簡單,但是對於UIView來說就比較麻煩。
- 使用上,實現上比較麻煩,需要注意的地方比較多,容易犯錯誤。
那麼有沒有不影響到原來的類的方式呢?
新思路
既然現有的類去實現這個協議比較麻煩,那麼找一個第三方類,永久的實現這個介面,並且把訊息轉發到現有的類不就可以了嗎。
我們都知道有一個類不繼承於NSObject
,功能就是代理,那麼我們利用這個類來做訊息轉發。
介面如下
@interface DDKeyPathChannelBaseProxy : NSProxy <DDKeyPathChannelProtocol>
// 以下兩個屬性確定物件唯一性
@property (readonly, nonatomic) NSInteger channelType;
@property (readonly, nonatomic) NSString *channelId;
// 原本的物件
@property (weak, readonly, nonatomic) __kindof NSObject *target;
- (instancetype)initWithChannelType:(NSInteger)channelType channelId:(NSString *)channelId target:(NSObject *)target;
@end
// 這是一個通過keyPath+白名單的實現,可以通過mapper來對映真正的keyPath
@interface DDKeyPathChannelProxy : DDKeyPathChannelBaseProxy
@property (strong, nonatomic) NSArray<NSString *> *whiteList;
@property (strong, nonatomic) NSDictionary<NSString *, NSString *> *keyPathMapper; // messageKeyPath : realKeyPath
@property (strong, nonatomic) void(^valueWillChangeBlock)(__kindof NSObject *target, NSString *keyPath, __kindof id newValue);
@property (strong, nonatomic) void(^valueDidChangeBlock)(__kindof NSObject *target, NSString *keyPath, __kindof id newValue);
@end
// 這是一個block的實現,可以在同步的時候自定義轉換與實現
@interface DDKeyPathBlockChannelProxy : DDKeyPathChannelBaseProxy
@property (strong, nonatomic) NSString *keyPath;
@property (strong, nonatomic) void(^valueChangedBlock)(__kindof NSObject *target, NSString *keyPath, __kindof id newValue);
@end
轉發的核心在於訊息的傳遞
// keyPath
- (void)setValue:(id)value forKey:(NSString *)key {
if (self.valueWillChangeBlock) self.valueWillChangeBlock(self.target, key, value);
[self.target setValue:value forKey:key];
if (self.valueDidChangeBlock) self.valueDidChangeBlock(self.target, key, value);
}
// block
- (void)setValue:(id)value forKey:(NSString *)key {
if ([key isEqualToString:self.keyPath]) {
if (self.valueChangedBlock) {
self.valueChangedBlock(self.target, key, value);
}
}
}
那麼我們怎麼去掛載這個代理物件呢,想到associate object,那麼我們也很容易的控制自己的生命週期了。
這樣,我們就不需要在現有類中實現方法來支援該功能了,而且這樣也更好的封裝遮蔽了這些比較特殊的功能。在實踐中感覺這種方式的使用成本是最低的,大家也比較容易接受。
[self.user1 bindChannelType:ChannelTypeUser
channelId:self.user1.id];
[self.user2 addChannelProxyWithChannelType:ChannelTypeUser
channelId:self.user2.id
config:^(DDKeyPathChannelProxy *proxy) {
proxy.keyPathMapper = @{ @"name": @"nickName" };
}];
// 更新屬性
[[DDKeyPathChannelManager sharedChannel] emitChannelType:ChannelTypeUser
channelId:@"1"
value:@"Tom"
forKeyPath:@"name"];
[[DDKeyPathChannelManager sharedChannel] emitChannelType:ChannelTypeUser
channelId:@"1"
value:@(30)
forKeyPath:@"age"];
UI層更新也可以通過這種方式,也可以選擇使用KVO。
題外話
關於這個功能,很多人肯定想到了ReactiveX
,關於這點,兩者的確有部分相似的場景,但也有很多不同的地方。
關於更新UI這點,兩者從效果上來看的確是一致的
object -> (signal, keyPath) -> UI
兩者最大的不同在於,ReactiveX
是monad的思想,是有輸入輸出,擁有明確的輸入物件和觀察物件,行為流程是從上游到下游。而本套方案是一箇中間人模式,是一個星狀結構,更像通知一點。
但是兩者思想是類似的,ReactiveX
是把各種行為封裝成Signal
,而我們是把訊息使用keyPath
來承載與轉發。
如果想要使用ReactiveX
來實現這個功能也不是不可以,建立一個全域性的熱型號(subject),控制好回收(dispose),也是可以實現該功能,但總感覺和RX的概念有點偏差了。
總結
從第一篇方案,到現在最終比較完美的一套方案,也是因為我們的需求在一步一步的變化,要求我們使用更好、更靈活的方案才能滿足的結果。這個過程是一個不斷思考不斷反思的過程,從這個方案的演化中,我深有感悟,很多東西在創造出來的時候看似完美,但實際上還有很大的完善空間,同時別人的方案也會對自己的想法有很多的幫助。所以多瞭解別人的實現方案對自己的提升還是很有幫助的。
相關文章
- 前端資料請求的終極方案前端
- DBeaverUltimate for Mac v23.3.1終極版:終極資料庫管理解決方案Mac資料庫
- JavaScript 資料型別檢測終極解決方案JavaScript資料型別
- 分散式資料物件:超級終端的"全域性變數"分散式物件變數
- Navicat Premium for Mac:多資料庫管理的終極解決方案REMMac資料庫
- 【Canal】資料同步的終極解決方案,阿里巴巴開源的Canal框架當之無愧!!阿里框架
- 全域性負載均衡方案負載
- 初識react(五) 資料流終極解決方案 dva(零配置)React
- 全域性CSS的終結(狗帶)CSS
- 資料庫同步方案資料庫
- Oracle資料庫終極恢復Oracle資料庫
- 資料庫效能提升終極指南資料庫
- 2.8.2 全域性資料服務GDS
- Linux 資料同步方案Linux
- 全球10大終極資料庫 - 下篇資料庫
- 全球10大終極資料庫 - 上篇資料庫
- 非同步程式設計的終極解決方案 async/await:用同步的方式去寫非同步程式碼非同步程式設計AI
- Android Studio終極配置方案Android
- 全文Feed的終極解決方案
- 【UniApp】-uni-app-全域性資料和區域性資料APP
- MySQL半同步複製資料最終一致性驗證MySql
- 不要打破鏈式呼叫!一個極低成本的RxJava全域性Error處理方案RxJavaError
- windows 資料夾檢視全域性生效Windows
- Golang 全域性sql資料庫連線GolangSQL資料庫
- uni-app全域性資料傳遞APP
- 二、修改資料庫全域性名稱資料庫
- 分散式全域性ID生成方案分散式
- 資料夾的隱藏(十)終極篇
- H5定位終極解決方案H5
- Exception in thread “main” 終極解決方案ExceptionthreadAI
- 前端(React)生成pdf終極解決方案(^_^)前端React
- mac php環境終極解決方案MacPHP
- asp.net Ajax 終極解決方案ASP.NET
- 終極自託管解決方案指南
- 2.6.2 確定全域性資料庫名稱資料庫
- 從全域性視角看資料結構資料結構
- EXCEL應用:資料視覺化終極教程Excel視覺化
- 資料庫管理軟體DBeaverUltimate for Mac終極版資料庫Mac