Farlanki

KVO

字数统计: 1.1k阅读时长: 4 min
2015/04/03 Share

kvo机制可以设置被监听者和监听者,当被监听者的某个属性发生变化时,调用监听者的相关方法。

1
2
3
4
5
6
7
8
9
10
11
//add observer
[self.radioRecordArray addObserver:self forKeyPath:@"downloadDidFinished" options:NSKeyValueObservingOptionNew context:Nil];

//响应函数
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
if([keyPath isEqualToString:@"downloadDidFinished"]){
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
}
}

以上实现的是异步下载,下载完毕在主线程更新view。
注意NSArray NSMutableArray均不支持kvo,可以创建一个NSArrayController来实现。

Swift中的kvo

要在被观察的变量前加 dynamic

KVO的实现

https://mikeash.com/pyblog/friday-qa-2009-01-23.html
KVO是通过Objective-C的运行时实现的.当你观察某个类的实例化对象,KVO会在运行时创建一个被观察对象的类的子类.在这个新的类中,被观察属性的set方法将被重载.之后,被观察对象的isa指针将被替换,所以被观察对象就变成了该新的类的对象.被重载的方法负责通知观察者.
苹果并不希望这个机制被暴露,所以子类还重载了 -class方法,当你调用这个方法时,返回的将是原本的类(本应该返回子类 o( ̄ヘ ̄*o) 机制的苹果).

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
66
67
68
69
70
71
72
73
74
75
76
// gcc -o kvoexplorer -framework Foundation kvoexplorer.m

#import <Foundation/Foundation.h>
#import <objc/runtime.h>


@interface TestClass : NSObject
{
int x;
int y;
int z;
}
@property int x;
@property int y;
@property int z;
@end
@implementation TestClass
@synthesize x, y, z;
@end

static NSArray *ClassMethodNames(Class c)
{
NSMutableArray *array = [NSMutableArray array];

unsigned int methodCount = 0;
Method *methodList = class_copyMethodList(c, &methodCount);
unsigned int i;
for(i = 0; i < methodCount; i++)
[array addObject: NSStringFromSelector(method_getName(methodList[i]))];
free(methodList);

return array;
}

static void PrintDescription(NSString *name, id obj)
{
NSString *str = [NSString stringWithFormat:
@"%@: %@\n\tNSObject class %s\n\tlibobjc class %s\n\timplements methods <%@>",
name,
obj,
class_getName([obj class]),
class_getName(obj->isa),
[ClassMethodNames(obj->isa) componentsJoinedByString:@", "]];
printf("%s\n", [str UTF8String]);
}

int main(int argc, char **argv)
{
[NSAutoreleasePool new];

TestClass *x = [[TestClass alloc] init];
TestClass *y = [[TestClass alloc] init];
TestClass *xy = [[TestClass alloc] init];
TestClass *control = [[TestClass alloc] init];

[x addObserver:x forKeyPath:@"x" options:0 context:NULL];
[xy addObserver:xy forKeyPath:@"x" options:0 context:NULL];
[y addObserver:y forKeyPath:@"y" options:0 context:NULL];
[xy addObserver:xy forKeyPath:@"y" options:0 context:NULL];

PrintDescription(@"control", control);
PrintDescription(@"x", x);
PrintDescription(@"y", y);
PrintDescription(@"xy", xy);

printf("Using NSObject methods, normal setX: is %p, overridden setX: is %p\n",
[control methodForSelector:@selector(setX:)],
[x methodForSelector:@selector(setX:)]);
printf("Using libobjc functions, normal setX: is %p, overridden setX: is %p\n",
method_getImplementation(class_getInstanceMethod(object_getClass(control),
@selector(setX:))),
method_getImplementation(class_getInstanceMethod(object_getClass(x),
@selector(setX:))));

return 0;
}

ClassMethodNames使用了Objective-C运行时方法来取得一个类所实现了的方法表.它只会取得直接在类中实现的方法,而不会取得在父类中实现的方法.PrintDescription将对象的信息打印出来.它通过两种方法,-class方法和一个Objective-C运行时方法,告诉我们该对象的类是什么,以及告诉我们该类中的方法的信息.
结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
control: <TestClass: 0x104b20>
NSObject class TestClass
libobjc class TestClass
implements methods <setX:, x, setY:, y, setZ:, z>
x: <TestClass: 0x103280>
NSObject class TestClass
libobjc class NSKVONotifying_TestClass
implements methods <setY:, setX:, class, dealloc, _isKVOA>
y: <TestClass: 0x104b00>
NSObject class TestClass
libobjc class NSKVONotifying_TestClass
implements methods <setY:, setX:, class, dealloc, _isKVOA>
xy: <TestClass: 0x104b10>
NSObject class TestClass
libobjc class NSKVONotifying_TestClass
implements methods <setY:, setX:, class, dealloc, _isKVOA>
Using NSObject methods, normal setX: is 0x195e, overridden setX: is 0x195e
Using libobjc functions, normal setX: is 0x195e, overridden setX: is 0x96a1a550

通过-class方法取得的类是TestClass,通过object_getClass取得是是该对象的真面目:一个NSKVONotifying_TestClass的对象.这就是那个动态创建的类.
NSKVONotifying_TestClass中重载了-setX,-setY方法,而并没有-setZ方法,因为没对象观察Z这个属性.同时,要留意所有被观察的TestClass类的对象都是被替换为同一个新的子类,意味着它们都拥有这些重载方法.例如,即使y对象只是其Y属性被观察,可是其-setX方法也被重载了.显然,苹果认为如果为每一种拥有不同的被观察属性组合的对象创建将会导致花费激增.
另外,-class方法也被重载了,为了隐藏动态子类的存在.
接下来,使用-methodForSelector方法显示setX方法的实现,两者的值是相同的.因为在动态子类中没有发现-methodForSelector方法的重载,我们可以推测出-methodForSelector方法使用了调用了-class方法,从而产生这个结果.所以我们使用一个Objective-C运行时方法,得到了和使用上一个方法不同的结果.

CATALOG
  1. 1. Swift中的kvo
  2. 2. KVO的实现