声明:本文仅供记忆使用,并不适合新手小白观看
KVC
- KVC的全称是Key-Value Coding,俗称“
键值编码”,可以通过一个key来访问某个属性 - 常见的API有
1 | - (void)setValue:(id)value forKeyPath:(NSString *)keyPath; |
- setValue:forKey:原理

- valueForKey:原理

KVO
-
KVO的全称是Key-Value Observing,俗称“
键值监听”,可以用于监听某个对象属性值的改变,主要通过isa-swizzling技术实现的 -
使用API
1
2
3
4
5
6
7
8
9
10
11
12
13// 注册观察者
NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
[self.person addObserver:self forKeyPath:@"name" options:options context:@"1111"];
// 当监听对象的属性值发生改变时,就会调用
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
NSLog(@"监听到%@的%@属性值改变了 - %@ - %@", object, keyPath, change, context);
}
- (void)dealloc {
// 移除监听
[self.person removeObserver:self forKeyPath:@"name"];
} -
实现原理

- runtime新生成一个
NSKVONotifying_xxx类对象继承原来的类对象,对象的isa指针指向它; - 重写
NSKVONotifying_xxxset和class方法; NSKVONotifying_xxx的set方法IMP指向Foundation框架_NSSetxxxValueAndNotify的实现;_NSSetxxxValueAndNotify的内部实现为:1
2
3[self willChangeValueForKey:@"age"];
[super setAge:age];
[self didChangeValueForKey:@"age"];didChangeValueForKey:内部会调用observer的observeValueForKeyPath:ofObject:change:context:方法
- runtime新生成一个
-
手动触发kvo
1
2[self.person willChangeValueForKey:@"age"];
[self.person didChangeValueForKey:@"age"]; -
使用
KVC会触发KVO,因为KVC内部会主动调用willChangeValueForKey:和didChangeValueForKey:
KVO注意事项
- 崩溃原因总结
- 添加监听后没有清除会导致闪退
- 清除不存在的key也会闪退
- 添加重复的key导致闪退
- observe忘记写监听回调方法 observeValueForKeyPath
- 监听者和被监听者的生命周期不同
- 自己解决,利用
@try@catch1
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
@implementation NSObject (MKVO)
+ (void)load{
[self switchMethod];
}
+ (void)switchMethod{
//移除kvo的方法
SEL removeSel = @selector(removeObserver:forKeyPath:);
SEL myRemoveSel = @selector(removeDasen:forKeyPath:);
//监听的方法
SEL addSel = @selector(addObserver:forKeyPath:options:context:);
SEL myaddSel = @selector(addDasen:forKeyPath:options:context:);
Method systemRemoveMethod = class_getClassMethod([self class],removeSel);
Method DasenRemoveMethod = class_getClassMethod([self class], myRemoveSel);
Method systemAddMethod = class_getClassMethod([self class],addSel);
Method DasenAddMethod = class_getClassMethod([self class], myaddSel);
//交换方法的实现
method_exchangeImplementations(systemRemoveMethod, DasenRemoveMethod);
method_exchangeImplementations(systemAddMethod, DasenAddMethod);
}
//利用@try @catch
// 交换后的方法
- (void)removeDasen:(NSObject *)observer forKeyPath:(NSString *)keyPath{
@try {//相对应解决方法1而已,只是把@try @catch 写在这里而已
[self removeDasen:observer forKeyPath:keyPath];
} @catch (NSException *exception) {
}
}
// 交换后的方法
- (void)addDasen:(NSObject *)observer forKeyPath:(NSString *)keyPath options:
(NSKeyValueObservingOptions)options context:(void *)context{
[self addDasen:observer forKeyPath:keyPath options:options context:context];
} - 使用JJException解决