看了一些资料,对OC更加深入了解,记录总结一下。
KVO:key-value-boserver,键-值-监听。主要是用来监听对象属性的变化。
一、KVO 的 简单用法
首先我们先写一个常规的KVO:
创建一个Person
类
#import
@interface Person : NSObject
@property (nonatomic,assign) NSInteger age;
@end
@implementation Person
@end
创建一个Person
对象p
,在touchesBegan
中更改p.age
的值
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
p = [[Person alloc] init];
p.age = 10;
[p addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew |NSKeyValueObservingOptionOld context:nil];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
NSLog(@"observeValueForKeyPath:ofObject:change:context");
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
p.age = 20;
}
然后点击view,就会触发observeValueForKeyPath
监听方法
二、对象p
的isa
指向
我们都知道instance
的isa
指向 类对象
,类对象
的isa
指向元类对象
。
所以我们利用函数 object_getClass
看看p
对象的isa
指向,发现:
添加监听前是isa指向是:Person
添加监听后是isa指向是:NSKVONotifying_Person
Class cls = object_getClass(p);//得到p的类对象
NSLog(@"%@", cls);
打印:Person
说明对象在添加KVO 的时候,利用runtime 动态生成了一个NSKVONotifying_
开头的类,而且该类是被监听类的子类
如果我们自己手动创建一个NSKVONotifying_Person
,会怎么样?
控制台会有这么一个打印:
KVO failed to allocate class pair for name NSKVONotifying_Person,
automatic key-value observing will not work for this class
所以综上所述和验证,可以得到结论:
在对象添加监听时,runtime会动态生成一个NSKVONotifying_
开头的类,并且该类继承自instance
对象所属类
三、KVO的内部实现
从另一个方向验证:我们可以看下setAge:的IMP
的实现
NSLog(@"1--%p",[p methodForSelector:@selector(setAge:)]);
[p addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew |NSKeyValueObservingOptionOld context:nil];
NSLog(@"2--%p",[p methodForSelector:@selector(setAge:)]);
打印如下:
2018-08-08 13:31:54.659785+0800 KVO[47390:1066243] 1--0x10724a610
2018-08-08 13:31:54.660092+0800 KVO[47390:1066243] 2--0x1076a6bf4
说明方法实现不是在一个类里也再一次证明:添加监听后,runtime 创建了新的类
我们利用断点进行调试
监听之前:
[Person setAge:]
监听之后:
Foundation _NSSetLongLongValueAndNotify
说明添加监听之后生成的
NSKVONotifying_Person
对象重写了setAge :
这个方法。
_NSSetLongLongValueAndNotify
是Foundation 框架的内部C函数,具体源码需要通过反编译工具去看,不展开讲了。
伪代码:
_NSSetLongLongValueAndNotify(){
[self willChangeValueForKey:@"age"];
[super setAge:xx];
[self didChangeValueForKey:@"age"];
}
- (void)didChangeValueForKey:(NSString *)key{
// 通知监听器
[oberser observeValueForKeyPath:key ofObject:self change:nil context:nil];
}
调用didChangeValueForKey
内部会调用observer
的 addObserver:forKeyPath:options:context:
方法
我们可以验证一下上述结论,由于我们不能重写NSKVONotifying_Person
的类的方法,所以我们利用继承关系来验证
@implementation Person
- (void)setAge:(NSInteger)age{
NSLog(@"setAge:");
}
- (void)willChangeValueForKey:(NSString *)key{
[super willChangeValueForKey:key];
NSLog(@"willChangeValueForKey:");
}
- (void)didChangeValueForKey:(NSString *)key{
NSLog(@"didChangeValueForKey: -- start");
[super didChangeValueForKey:key];
NSLog(@"didChangeValueForKey: -- end");
}
@end
打印如下:
1.willChangeValueForKey:
2.setAge:
3.didChangeValueForKey: -- start
4.observeValueForKeyPath:ofObject:change:context
5.didChangeValueForKey: -- end
所以按照这个执行顺序,我们可以验证我们上述伪代码。
四、KVO的一些细节
打印出一个类的对象方法
- (void)methodNameOfClass:(Class)cls{
unsigned int count;
Method *methodList = class_copyMethodList(cls, &count);
NSMutableArray *arr = [NSMutableArray array];
for (int i = 0 ; i < count; i++) {
Method method = methodList[I];
[arr addObject:NSStringFromSelector(method_getName(method))];
}
NSLog(@"%@",arr);
free(methodList);
}
调用methodNameOfClass
[self methodNameOfClass:object_getClass(p)];
打印如下:
(
"setAge:",
class,
dealloc,
"_isKVOA"
)
除了setAge :
方法还有class
,dealloc
,_isKVOA
class
:返回类对象
dealloc
:销毁等一系列操作
_isKVOA
:是否是KVO对象,return YES
值得考究的是class
这个函数,我们调用一下这个函数
[p class]
打印如下:
Person
为什么是Person
?
因为重写了class
函数,并将Person
类返回,苹果并不想让我们知道添加KVO监听,会动态生成一个新的派生类
五、如何手动调用KVO
很简单
[被监听对象 willChangeValueForKey:key];
[被监听对象 didChangeValueForKey:key];
如果不写willChangeValueForKey
,不会触发监听方法,因为didChangeValueForKey
内部会判断willChangeValueForKey
之前是否被调用过
总结
1 添加KVO时,会利用runtime动态生成一个NSKVONotifying_
开头的类。
2 NSKVONotifying_
类继承自被监听的类,也就是instance对象的isa
指针指向NSKVONotifying_
类,且NSKVONotifying_
类superClass
指向被监听的类。
3 NSKVONotifying_
类重写了class
方法并返回被监听的类
4 KVO 的本质是重写了set
方法,一个对象属性能否被监听,主要是看该属性有没有set
方法