全局数据同步(二)UI篇

达芬奇密码2018-07-19 10:09

这是对上篇客户端全局数据同步方案一个微小的补充。

上次讨论了如何让数据全局同步,但是在同步到UI层的时候还是有些麻烦。

之前的方案有两种:

  1. 在viewWillAppear的时候,reloadData,缺点是如果需要reload的数据太多,大量计算会导致阻塞主线程,虽然可能没有那么严重,但是有些时候还是能够感知出来。
  2. 使用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>
// 我们需要在创建的时候就确定类型,而且不能修改,防止意料之外的情况
// type也可以强制指定为某个类型,确保不会中途变更
- (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;
}

由于该页面资源id并不会变化,所以只需要在初始化的时候绑定一次id就可以了,上面的KVO代码就可以全部去除了。

[self.recordTabbar bindId:self.record.id];

这样只要提交了属性变化的消息(emit),也会自动同步到这里,从而自动更新视图。

这样我们的后半部分流程(从数据到显示)也完整了,整个流程都依赖于MZChannel进行。

本文来自网易实践者社区,经作者段家顺授权发布。