- 老司机们,快上车
- 回到标题的内容: 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有什么疑问或者建议,欢迎留言