变更记录

序号 录入时间 录入人 备注
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 子属性
1
2
3
4
5
6
7
//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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
//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 中注册观察
1
2
3
4
5
6
7
8
9
10
11
//使用到 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 中移除观察
1
2
3
4
- (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 方法不会被调用
1
2
3
4
5
6
7
- (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 避免了重复注册,所以不用担心重复注册问题
1
2
3
4
5
- (void)viewWillAppear:(BOOL)animated
{
...
[self.tableViewMain reloadRowsAtIndexPaths:self.tableViewMain.indexPathsForVisibleRows withRowAnimation:UITableViewRowAnimationNone];
}
7. 若要实现正确的 KVO, 在对 KeyPath 子属性赋值时一定要使用 setValue:forKey: 方法
1
[self setValue:[self statusDescription] forKey:KEY_PATH_FOR_STATUS_DESCRIPTION];

效果图

(无)

备注

(无)