基本使用:
说明:HJPerson是继承自NSObject的自定义类
@interface ViewController ()
@property(nonatomic, strong) HJPerson *person1;
@property(nonatomic, strong) HJPerson *person2;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.person1 = [[HJPerson alloc] init];
self.person1.age = 11;
self.person2 = [[HJPerson alloc] init];
self.person2.age = 22;
NSKeyValueObservingOptions options = NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew;
[self.person1 addObserver:self forKeyPath:@"age" options:options context:@"123"];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
NSLog(@"监听到了%@的%@的属性值改变了 -- %@ --- %@",object,keyPath,change,context);
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
self.person1.age = 111;
self.person2.age = 222;
}
@end
上面的代码对 person1 的 age 属性进行了监听,在对 person1 和 person2 做新值改变的时候都会走 HJPerson 的 Set 方法,但是为什么一个就能被监听,而另一个却不行。所以我们打断点看看 person1 和 person2 的 isa 指针到底做了什么?
通过打印我们看到两个对象的 isa 指针不太一样,也就是说:我们的对象一但添加了监听,那他的 isa 指针就不是指向他原来的类对象,而是指向了另一个类对象。没添加监听的,他的类对象还是原来的。但是我们项目中并没有声明或者创建过这样的类对象,这个对象是哪里来的?
- 这个是系统 Runtimer 动态创建的类,这个是HJPerson的子类,他们的继承关系就是下面的这张图
oc 中的方法调用是以消息为准的,那我们上面的这个代码 self.person1.age = 11;
这个实际就是找到 isa 指针的对象进行发消息,那系统动态生成的这个类大致做了一个什么事情,我们在自己的代码里面模仿声明一个 NSKVONotifying_HJPerson 这样的类,这个类中的用伪代码大致实现这个逻辑:
- (void)setAge:(NSInteger)age{
_NSSetIntValueAndNotify();
}
void _NSSetIntValueAndNotify{
[self willChangeValueForKey:@"age"];
[super setAge:age];
[self didChangeValueForKey:@"age"];
}
- (void)didChangeValueForKey:(NSInteger)age{
///< 通知监听器。某某属性值发生了改变
[observer observeValueForKeyPath:age ofObject:self change:nil context:nil];
}
系统生成的这个类他自己会有SetAge的这个方法,它自己会调用Founction框架中的_NSSetIntValueAndNotify方法,他大致做了三件事,监听属性将要改变,之后对父类的成员变量进行赋值,之后通过监听者把消息发送出去。这个就是伪代码的实现,可以通过反编译的方式去越狱之后拿到 Founction 文件之后,用Hopper反编译工具,去查看_NSSetIntValueAndNotify 方法。
- 怎样才能知道 _NSSetIntValueAndNotify 这个方法确定存在,我们通过打印 IMP 来看一下
NSLog(@"person1 添加KVO之前的监听 %@ %@",object_getClass(self.person1),object_getClass(self.person2));
NSLog(@"person1 添加KVO之前的监听(方法) %p %p",
[self.person1 methodForSelector:@selector(setAge:)],
[self.person2 methodForSelector:@selector(setAge:)]
);
NSKeyValueObservingOptions options = NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew;
[self.person1 addObserver:self forKeyPath:@"age" options:options context:@"123"];
NSLog(@"person1 添加KVO之后的监听 %@ %@",object_getClass(self.person1),object_getClass(self.person2));
NSLog(@"person1 添加KVO之前的监听(方法) %p %p",
[self.person1 methodForSelector:@selector(setAge:)],
[self.person2 methodForSelector:@selector(setAge:)]
);
- 我们可以看到监听之后的方法发生了改变。也就是说添加监听之后他的方法实现就变成了_NSSetLongLongValueAndNotify 这个方法实现,也就是说运行时生成的这个类,确实是调用了Founction框架中的_NSSetLongLongValueAndNotify。
- 现在我们来验证我们伪代码中的调用逻辑是否正确,在HJPerson类中。我们做一个这样的操作
- (void)setAge:(NSInteger)age{
_age = age;
NSLog(@" setAge ---> %zd",age);
}
- (void)willChangeValueForKey:(NSString *)key{
[super willChangeValueForKey:key];
NSLog(@"willChangeValueForKey");
}
- (void)didChangeValueForKey:(NSString *)key{
NSLog(@"didChangeValueForKey----- begin");
[super didChangeValueForKey:key];
NSLog(@"didChangeValueForKey----- end");
}
我们可以看到这个里面打印顺序,
willChangeValueForKey
setAge
didChangeValueForKey----- begin
接收到监听的通知打印
didChangeValueForKey----- end
这样就验证了我们刚才在伪代码中实现的打印顺序
系统运行时生成的类做了什么:
NSLog(@"类对象 -- > %@----%@",object_getClass(self.person1),object_getClass(self.person2));
NSLog(@"类对象 -- > %@---%@",[self.person1 class],[self.person2 class]);
我们发现用运行时获取类对象 和 通过 class方法 获取类对象得到的结果是不同的,为什么会产生这样的结果的?也就是说,通过runtime拿出来的isa 所指向的对象为真实对象,而通过 class 拿出来的对象就不一定了。也就是说,rutime 在生成这个类的时候重写了class方法。所以用 class 调用的还是 HJPerson 的这个类(屏蔽了内部实现,不让我们知道NSKVONotifying_HJPerson 这个类的实现)
现在我们回到上面的继承关系的哪张图。我们怎么知道系统生成的类,有那些方法
- (void)printMethordNameOfClass:(Class)cls{
unsigned int outCount;
///< 获取方法列表
Method *methodList = class_copyMethodList(cls, &outCount);
NSMutableString * methordNames = [NSMutableString string];
///< 遍历所有的方法
for (int i = 0; i< outCount; i++) {
Method methord = methodList[I];
NSString *methordName = NSStringFromSelector(method_getName(methord));
[methordNames appendString:methordName];
[methordNames appendString:@" , "];
}
NSLog(@"methordNames --- > %@",methordNames);
free(methodList);
}
- (void)viewDidLoad {
[super viewDidLoad];
[self printMethordNameOfClass:object_getClass(self.person1)];
[self printMethordNameOfClass:object_getClass(self.person2)];
}
通过运行时打印出方法列表,我们就知道他内部的方法了。
所以通过上面这些。我们就知道KVO做了那些操作
- 利用RuntimeAPI 动态生成了一个子类,并且让Instance对象指向了这个动态生成的子类。
- 当修改instance对象的属性时,回调用Founction的_NSSetLongLongValueAndNotify函数
- willChangeValueForKey
2.父类原有的setter方法
3.didChangeValueForKey
4.内部触发监听器(oberser)的监听方法(observeValueForKeyPath:ofObject:change:context:)
手动触发KVO
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
[self.person1 willChangeValueForKey:@"age"];
[self.person1 didChangeValueForKey:@"age"];
}
那些属性可以监听
有setter方法的都可以监听。因为系统运行时创建的类,就是通过替换了原有的
setter方法的实现。
直接修改成员变量会触发KVO嘛
@interface HJPerson : NSObject{
@public
NSInteger _age;
}
self.person1->_age = 2;
这样是不会触发KVO的,因为KVO 的本质是修改Setter方法。这样修改成员变量是不可以的