- 老司机们,快上车
- 回到标题的内容: KVO浅析
- 先来看一下系统原生的效果:
#import "ViewController.h"
#import "LJModel.h"
@interface ViewController ()
@property (strong, nonatomic) LJModel *model;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
LJModel *model = [[LJModel alloc]init];
// 赋值
_model = model;
// 添加观察者 ,监听model的 name 属性的值的 改变
[model addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:@"ViewController"];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
NSLog(@"%@",_model.name);
// 点击三次的打印结果为:
/*
2016-07-12 12:56:31.453 answerDemo[2584:753037] 小强 1 号
2016-07-12 12:56:40.509 answerDemo[2584:753037] 小强 2 号
2016-07-12 12:56:42.195 answerDemo[2584:753037] 小强 3 号
*/
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
// 用于判断每次点击,都给name属性赋值
static int i = 0;
i ++;
// 通过set方法 给model的name赋值
_model.name = [NSString stringWithFormat:@"小强 %d 号",i];
}
@end
我们新建一个继承自
NSObject的LJModel类给这个类添加一个
name属性@property (copy, nonatomic) NSString *name;在
viewDidload的方法中,我们创建一个LJModel对象 model,并给全局的model属性赋值
给
model添加一个观察者(当前的ViewController控制器),用来监听name属性的值的变化最后重写
(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context这个方法,并在这个方法里面输出一下model的name属性的值点击三次屏幕,可以看到,每次
name属性的赋值都可以成功的被监听
这个是如何实现的呢?
- 我们知道,要想监听某个值的改变,我们可以在
set方法中去监听 - 比如网络请求,我们就可以在set方法中拿到请求回来的数据来刷新界面
那我们现在来做这样一件事情:
#import <Foundation/Foundation.h>
@interface LJModel : NSObject
{
@public
NSString *_name;
}
@end
我们不用
@property修饰name,然后也不提供set和get方法然后在点击屏幕的时候,直接访问
model的成员变量_name进行赋值:
// 直接访问成员变量进行赋值
_model -> _name = [NSString stringWithFormat:@"小强 %d 号",i];
- 我们再在
(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context打印_name:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
NSLog(@"%@",_model -> _name);
}
此时再点击屏幕,
debug没任何输出,这个时候观察者ViewController就监听不到_name的值得变化了然后我们把
model的_name改成用@property修饰的name这时候系统就会自动生成
_name属性,并且生成get和set方法,我们就可以通过点语法赋值和取值了-
现在的问题就是,我们没有重写
model的setName方法,ViewController是怎么通过set方法监听的呢- 憋说话,看图:


我们在给
model添加观察者的地方打个断点,上面两张图是单步调试之后的结果我们可以看到没给
model添加观察者之前,isa(暂时先简单的理解成指针,这里不做探讨)指针指向的是LJModel这个类添加
观察者之后,isa指针指向了NSKVONotifying_LJModel,这TM是个什么鬼我们在创建类的时候,系统都会自动自动生成一个
isa指针,指向这个类,当这个类或这个类的实例化对象调用这个方法的时候,会先根据isa找到对应的类,来判断有没有这个方法,有则调用,没有就会报错显然在这里,系统修改了我们的
isa指针,那我们在调用model的setName方法就会去NSKVONotifying_LJModel里面找,然后这里又没有报错,这说明NSKVONotifying_LJModel这个类里面有setName方法,并且对model的name属性成功的监听想要监听
model的name的值得改变,我们可以在model里面重写setName方法,但是这里系统已经将isa指针指向了NSKVONotifying_LJModel这个类,并不是LJModel这个类这时候,我们可以大概猜测一下
NSKVONotifying_LJModel这个类是LJModel的分类或者子类,因为LJModel的分类或者子类可以重写setName方法, 然后系统重写了setName方法,成功的对name的值得改变进行监听但是又因为分类重写方法,系统会优先调用分类的方法,这样就会覆盖原有类的方法,如果原有类重写了
setName,那被分类覆盖掉之后,导致重写失败,KVO只是监听值得改变,并不会覆盖掉原有类的方法,所以,这个类应该是子类,然后在子类重写父类的方法,并且调用[super setName]方法,从而在不影响父类的值得情况下,对父类进行监听
到这里之后,我们尝试的写了一下,简单的仿照系统,自己搞个可以监听name的值得变化的方法:
- 开始我们先来看一下系统的.
com + 左键到系统的方法里面去,进去之前,系统会提示有三个方法,先随便选一个,然后找到NSObject(NSKeyValueObserverRegistration):
@interface NSObject(NSKeyValueObserverRegistration)
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(nullable void *)context NS_AVAILABLE(10_7, 5_0);
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
@end
- 可以看到,这里面有好几个分类,都有这个方法,这里因为
LJModel是直接继承NSObject的,所以,model在调用这个方法的时候,会选择NSObject的分类的方法
既然这样,我们就
com + C和com + V然后我们也搞一个这样的方法,为了和系统的方法区分开来,我们给方法改个名字,加上自己的前缀:
#import "NSObject+Extension.h"
#import <objc/runtime.h>
#import "LJSubModel.h"
@implementation NSObject (Extension)
- (void)lj_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context {
// 将当前对象的指针指向观察者
object_setClass(self, [LJSubModel class]);
// 给当前对象添加一个观察者(ViewController)的属性
objc_setAssociatedObject(self, "observer", observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
- 我们仿照系统,创建
NSObject的分类和一个LJModel的子类 - 在分类的这个方法里面,将当前类的
isa指向子类,这样,model调用setName就会到subModel里面掉setName方法 - 然后将
观察者动态添加到subModel的属性
然后看一下subModel的setName方法的实现:
#import "LJSubModel.h"
#import <objc/runtime.h>
@implementation LJSubModel
- (void)setName:(NSString *)name {
[super setName:name];
// 获取观察者
id observer = objc_getAssociatedObject(self, @"observer");
// 通知观察者调用方法
[observer observeValueForKeyPath:@"name" ofObject:observer change:nil context:nil];
}
@end
最后调用自己的方法实现监听:
// 添加观察者 ,监听model的 name 属性的值的 改变
[model lj_addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
首先我们调用
[super setName:name];,这样就可以给model.name赋值了然后拿到观察者,并手动调用
[observer observeValueForKeyPath:@"name" ofObject:self change:nil context:nil]; }这里就相当于外面
viewController调用这个方法,并将ViewController作为作为观察者这样,我们在
ViewController里面用model调用我自己的方法, 然后实现obserVerForKeyPath的时候,就可以监听到name的值得改变了这时候,我们再来看一下:

这样,用我们自己的方法,也可以实现简单的KVO了
KVO很强大,有很多值的我们探究的地方,这里只是简单的介绍了一下KVO的实现机制,希望能帮到大家
demo传送门:https://github.com/lauding/answerDemo
另外附上一篇个人觉得比较好的介绍KVO的实现的博客:http://www.cocoachina.com/ios/20150313/11321.html
对我的Demo有什么疑问或者建议,欢迎留言