第九块石头

可变长度对象在KVO中的实现

| Comments

KVO是降低代码耦合度的一种很有效的方法,以往我都是对某个实例的属性进行监控,获取他的变化情况。其本质上是监控属性的地址的变化。然而对于可变长度的对象,如NSMutableArrayNSMutableDictionary等,向这种对象进行添加成员,其的地址不会发生变化,普通的KVO方法自然就不能用了。该如何监控可变长度对象的内容变化呢?

Google了一下,又查阅了官方文档,目前有两种方法:

  • 手动实现insert方法和remove方法 文档
  • 调用系统的mutableValueForKey:方法,自动触发KVO 文档

第一种方法具体实现逻辑还不是很清楚,有待进一步深入学习Key-Value编程。

第二种方法使用了Proxy。

来个Demo:

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
@interface AA : NSObject

@property (strong, nonatomic) NSMutableArray *a;

@end

@implementation AA

- (id)init {
    if (self = [super init]) {
        self.a = [[NSMutableArray alloc] init];
    }
    return self;
}

- (void)addSomeObjects {
    [self.a addObject:@"a"];
    [self.a addObject:@"b"];
}

@end

@interface BB : NSObject

@property (strong, nonatomic) AA *aClass;

@end

@implementation BB

- (id)init {
    if (self = [super init]) {
        self.aClass = [[AA alloc] init];
        [self.aClass addObserver:self
                      forKeyPath:@"a"
                         options:NSKeyValueObservingOptionNew
                         context:NULL];
        [self.aClass addSomeObjects];
    }
    return self;
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
    NSLog(@"object: %@ keyPath: %@ property: %p", object, keyPath, self.aClass.a);
}
@end

我们会发现observe方法无效,无任何log输出。

采用第一种手动方法:

将AA的实现修改为以下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@implementation AA
- (id)init {
    if (self = [super init]) {
        self.a = [[NSMutableArray alloc] init];
    }
    return self;
}

- (void)insertObject:(NSObject *)object inAAtIndex:(NSUInteger)index {
    [self.a insertObject:object atIndex:index];
}
- (void)insertA:(NSArray *)array atIndexes:(NSIndexSet *)indexes {
    [self.a insertObjects:array atIndexes:indexes];
}
- (void)removeAAtIndexes:(NSIndexSet *)indexes {
    [self.a removeObjectsAtIndexes:indexes];
}

- (void)addSomeObjects {
    [self insertObject:@"a" inAAtIndex:[self.a count]];
    [self insertA:@[@"b"] atIndexes:[NSIndexSet indexSetWithIndex:[self.a count]]];
    [self removeAAtIndexes:[NSIndexSet indexSetWithIndex:0]];
}
@end

得到Log:

1
2
3
object: <AA: 0xfd06730> keyPath: a property: 0x6c20dd0
object: <AA: 0xfd06730> keyPath: a property: 0x6c20dd0
object: <AA: 0xfd06730> keyPath: a property: 0x6c20dd0

会不会感觉很麻烦?不能直接用[self.a addObject:@"a"]而得必须用[self insertA:@[@"a"] atIndexes:[NSIndexSet indexSetWithIndex:[self.a count]]]或者[self insertObject:@"a" inAAtIndex:[self.a count]];!没办法,KVO会在这两个方法内的方法执行完成后触发,所以必须显式调用。

这三个方法不是必须全部实现,需要哪个实现哪个方法

第二种方法就比较简单了,在原有基础上,只是修改addSomeObjects方法:

1
2
3
4
- (void)addSomeObjects {
    [[self mutableArrayValueForKey:@"a"] addObject:@"a"];
    [[self mutableArrayValueForKey:@"a"] removeObject:@"a"];
}
1
2
object: <AA: 0x6e61990> keyPath: a property: 0x6e94b90
object: <AA: 0x6e61990> keyPath: a property: 0x6e94d50

然而,不难发现,对象地址变了!!!!0x6e94b90=>0x6e94b50,虽然可能一般不出现问题,但是如果你之前把a赋值给另外一个变量,那么两者就不是同一个了,这是很危险的一种行为!

怎么办?把上面的两个方法合并起来用~~最后AA就变成了这样的代码:

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
@interface AA : NSObject

@property (strong, nonatomic) NSMutableArray *a;

@end

@implementation AA
- (id)init {
    if (self = [super init]) {
        self.a = [[NSMutableArray alloc] init];
    }
    return self;
}

- (void)insertObject:(NSObject *)object inAAtIndex:(NSUInteger)index {
    [self.a insertObject:object atIndex:index];
}
- (void)insertA:(NSArray *)array atIndexes:(NSIndexSet *)indexes {
    [self.a insertObjects:array atIndexes:indexes];
}
- (void)removeAAtIndexes:(NSIndexSet *)indexes {
    [self.a removeObjectsAtIndexes:indexes];
}
- (void)addSomeObjects {
    [[self mutableArrayValueForKey:@"a"] addObject:@"a"];
    [[self mutableArrayValueForKey:@"a"] removeObject:@"a"];
}
@end

这种写法将系统的自动KVO手动实现了,所以不再是重新生成一个对象a,因此地址不变!

Comments