上次討論了如何讓數據全局同步,但是在同步到UI層的時候還是有些麻煩。現在來解決UI層的問題。
之前的方案有兩種:
- 在viewWillAppear的時候,reloadData,缺點是如果需要reload的數據太多,大量計算會導致阻塞主線程,雖然可能沒有那么嚴重,但是有些時候還是能夠感知出來。
- 使用KVO來監聽變化,缺點是代碼侵入性太強,而且嚴重影響了一些代碼的統一性。
下面是使用KVO的一個例子:
@weakify(self);
[self.KVOController observe:_record keyPath:NSStringFromSelector(@selector(praiseCount)) options:NSKeyValueObservingOptionNew block:^(id _Nullable observer, MZRecord *object, NSDictionary<NSString *,id> * _Nonnull change) {
@strongify(self);
self.recordTabbar.praiseCount = object.praiseCount;
}];
[self.KVOController observe:_record keyPath:NSStringFromSelector(@selector(praised)) options:NSKeyValueObservingOptionNew block:^(id _Nullable observer, MZRecord *object, NSDictionary<NSString *,id> * _Nonnull change) {
@strongify(self);
[self.recordTabbar setPraised:object.isPraised animated:self.view.window != nil];
}];
[self.KVOController observe:_record keyPath:NSStringFromSelector(@selector(collected)) options:NSKeyValueObservingOptionNew block:^(id _Nullable observer, MZRecord *object, NSDictionary<NSString *,id> * _Nonnull change) {
@strongify(self);
[self.recordTabbar setCollected:object.collected animated:self.view.window != nil];
}];
[self.KVOController observe:_record keyPath:NSStringFromSelector(@selector(collectCount)) options:NSKeyValueObservingOptionNew block:^(id _Nullable observer, MZRecord *object, NSDictionary<NSString *,id> * _Nonnull change) {
@strongify(self);
self.recordTabbar.collectCount = object.collectCount;
}];
[self.KVOController observe:_record keyPath:NSStringFromSelector(@selector(commentCount)) options:NSKeyValueObservingOptionNew block:^(id _Nullable observer, MZRecord *object, NSDictionary<NSString *,id> * _Nonnull change) {
@strongify(self);
self.recordTabbar.commentCount = object.commentCount;
}];
上面是我們為了同步3個按鈕的狀態的代碼,我們使用了一個第三方庫來簡化KVO的編寫,但還是非常的冗余。
為此,開始思考有什么更簡單的方法。
開始我們想要封裝KVO,直接綁定數據和UI,但是很多數據并不是一一對應的,比如數字,狀態,是需要轉化的,而且狀態變更很多情況下是需要動效的,所以無論如何都不免不了監聽和轉換這兩個東西。
后來想,既然數據可以做全局同步,那么是否可以把視圖也看作一種類型的資源,也自動同步該狀態屬性呢?按照這種思路,將視圖改寫支持這種方式來同步。
@interface UIView (MZChannel) <MZChannelProtocol>
// 我們需要在創建的時候就確定類型,而且不能修改,防止意料之外的情況
- (instancetype)initWithFrame:(CGRect)frame channelType:(NSInteger)channelType;
// 加入數據池中,并且內部增加了lock,保證線程安全
- (void)bindId:(NSString *)id;
// 為了避免與view自身屬性沖突,增加了一個白名單配置
- (NSArray<NSString *> *)channelWhiteList;
@end
查看一下我們修改之后的狀態
// RecordTabbar
// 新增這兩個方法,由于之前設計中的接口與該keyPath統一,所以其他內容不需要修改
- (NSInteger)channelType {
return MZResourceTypeNote;
}
- (NSArray<NSString *> *)channelWhiteList {
return @[NSStringFromSelector(@selector(praised)),
NSStringFromSelector(@selector(praiseCount)),
NSStringFromSelector(@selector(collected)),
NSStringFromSelector(@selector(collectCount)),
NSStringFromSelector(@selector(commentCount))];
}
// 部分對應的setter方法
- (void)setPraised:(BOOL)praised {
_praised = praised;
[self.praiseButton setPraised:praised animated:self.window != nil];
}
- (void)setPraised:(BOOL)praised animated:(BOOL)animated {
_praised = praised;
[self.praiseButton setPraised:praised animated:animated];
}
- (void)setPraiseCount:(NSInteger)praiseCount {
_praiseCount = praiseCount;
self.praiseButton.praiseCount = praiseCount;
}
- (void)setCollected:(BOOL)collected {
[self setCollected:collected animated:self.window != nil];
}
由于該頁面資源id并不會變化,所以只需要在初始化的時候綁定一次id就可以了。
[self.recordTabbar bindId:self.record.id];
這樣我們的后半部分流程(從數據到顯示)也完整了,整個流程都依賴于MZChannel進行。