上次讨论了如何让数据全局同步,但是在同步到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进行。
网友评论