变更记录

序号 录入时间 录入人 备注
1 2016-05-04 Alfred Jiang -

方案名称

KVO - 使用 KVO 更新 UITableViewCell 显示

关键字

KVO \ UITableViewCell \ 观察属性

需求场景

  1. 通过 KVO 实现 UITableViewCell 自更新,避免重复 Reload Cell

参考链接

  1. Stack Overflow - adding KVO to UITableViewCell
  2. Nachbaur - Back to Basics: Using KVO

详细内容

1. 在 ObjectiveModel 数据模型中定义观察子属性 KeyPath, 示例表示要观察 statusDescription 子属性
//ObjectiveModel.h 中

#define KEY_PATH_FOR_STATUS_DESCRIPTION    @"statusDescription"

...
@property (nonatomic, strong) NSString     *statusDescription;
...
2. 定义 UITableViewCell 的子类 HKTimelineCell, 并实现监听方法 - (void)observeValueForKeyPath:(NSString )keyPath ofObject:(id)object change:(NSDictionary )change context:(void )context*
//HKTimelineCell.h 中
...
//需要注册的属性
@property (strong, nonatomic) ObjectiveModel *request;

...
//注册和移除观察接口
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context;
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(void *)context;

...

//HKTimelineCell.m 中

@interface NetworkingTestRequestCell()

// 使用 ObservableKeys 保存 keyPath 观察状态,避免重复注册和重复移除(重复移除会导致 crash)
@property (nonatomic, strong) NSMutableSet *ObservableKeys;

@end

...

// 为 HKTimelineCell 的 request 属性注册观察(观察 request 中 KeyPath 为 KEY_PATH_FOR_STATUS_DESCRIPTION 的子属性), 此处的 request 为 ObjectiveModel 类型对象注册观察
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context
{
    if ([_ObservableKeys containsObject:keyPath]) {
        return;
    }

    if (!_ObservableKeys) {
        _ObservableKeys = [NSMutableSet set];
    }

    [_ObservableKeys addObject:keyPath];

    [self.request addObserver:observer
                   forKeyPath:keyPath
                      options:options
                      context:context];
}

//移除对 request 中 KeyPath 为 KEY_PATH_FOR_STATUS_DESCRIPTION 的子属性的观察
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(nullable void *)context
{
    if (![_ObservableKeys containsObject:keyPath]) {
        return;
    }

    [self.request removeObserver:observer forKeyPath:keyPath context:context];
    [_ObservableKeys removeObject:keyPath];
}

//观察 HKTimelineCell 的 request 属性中子属性(KeyPath 为 KEY_PATH_FOR_STATUS_DESCRIPTION)值的变化并更新 labelStatus 显示,此处的 request 为 ObjectiveModel 类型对象
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    if ([KEY_PATH_FOR_STATUS_DESCRIPTION isEqualToString:keyPath]) {

        //        NSString *oldObject = [change objectForKey:NSKeyValueChangeOldKey];
        NSString *newObject = [change objectForKey:NSKeyValueChangeNewKey];

        self.labelStatus.text = newObject;
    }
}
3. 在 - (void)tableView:(UITableView )tableView willDisplayCell:(HKTimelineCell )cell forRowAtIndexPath:(NSIndexPath )indexPath* 中注册观察
//使用到 HKTimelineCell 的 UITableview DataSource

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    // Don't add observers, or the app may crash later when cells are recycled
}

- (void)tableView:(UITableView *)tableView willDisplayCell:(HKTimelineCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
    [cell addObserver:cell forKeyPath:KEY_PATH_FOR_STATUS_DESCRIPTION options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) context:(void *)cell];
}
4. 在 - (void)tableView:(UITableView )tableView didEndDisplayingCell:(HKTimelineCell )cell forRowAtIndexPath:(NSIndexPath )indexPath* 中移除观察
- (void)tableView:(UITableView *)tableView didEndDisplayingCell:(HKTimelineCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
     [cell removeObserver:cell forKeyPath:KEY_PATH_FOR_STATUS_DESCRIPTION context:(void *)cell];
}
5. 在 - (void)viewWillDisappear:(BOOL)animated 中移除全部观察,因为页面离开时 didEndDisplayingCell 方法不会被调用
- (void)viewWillDisappear:(BOOL)animated
{
    ...
    [self.tableViewMain.visibleCells enumerateObjectsUsingBlock:^(NetworkingTestRequestCell *cell, NSUInteger idx, BOOL * _Nonnull stop) {
        [cell removeObserver:cell forKeyPath:KEY_PATH_FOR_STATUS_DESCRIPTION context:(void *)cell];
    }];
}
6. 在 - (void)viewWillAppear:(BOOL)animatedreload 当前显示的 cells, 注册被 viewWillDisappear 移除的观察,因为 cell 使用了 ObservableKeys 避免了重复注册,所以不用担心重复注册问题
- (void)viewWillAppear:(BOOL)animated
{
    ...
    [self.tableViewMain reloadRowsAtIndexPaths:self.tableViewMain.indexPathsForVisibleRows withRowAnimation:UITableViewRowAnimationNone];
}
7. 若要实现正确的 KVO, 在对 KeyPath 子属性赋值时一定要使用 setValue:forKey: 方法
[self setValue:[self statusDescription] forKey:KEY_PATH_FOR_STATUS_DESCRIPTION];

效果图

(无)

备注

(无)

results matching ""

    No results matching ""