博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
KVO讲解
阅读量:5323 次
发布时间:2019-06-14

本文共 6296 字,大约阅读时间需要 20 分钟。

最近一直在写swift项目,没有时间更新自己的技术博客,以前在博客里面写过KVO的底层原理,今天我们来看一下KVO的整个使用过程和使用场景(附有demo),大约花大家10-15分钟时间,希望大家看完博客之后对KVO的使用有更清醒的认识。

下面我们按照以下提纲讲解KVO。

  1. KVO的基本使用
  2. KVO的触发模式
  3. KVO的属性依赖
  4. KVO的原理探究
  5. 自定义KVO
  6. KVO对容器类的监听

 

一、KVO的基本使用

 1.基本步骤

  1. 通过addObserver:forKeyPath:options:context:注册观察者,观察者可以接收keyPath属性的变化事件。
  2. 在观察者中实现observeValueForKeyPath:ofObject:change:context方法,当keyPath属性发生改变后,KVO会回调这个方法来通知观察者。
  3. 当观察者不需要监听时,可以调用removeObserve:forKeyPath方法将KVO移除,需要注意的是,调用removeObserve需要在观察者消失之前,否则会导致Crash。

在注册观察者时,可以传入下列参数:

  •  Observer参数,观察者对象
  • keyPath参数 需要观察者的属性,由于是字符串形式,如果传错格式,容易导致Crash。一旦利用系统的反射机制NSStringFromSelector(keyPath)
  • options参数 参数是一个枚举类型
  1. NSKeyValueObserveOptionNew 接收新值,默认为只接受新值
  2. NSKeyValueObserveOptionOld 接收旧值
  3. NSKeyValueObserveOptionInitial 在注册时接收一次回调,在改变时也会发送通知
  4. NSKeyValueObserveOptionPrior 改变之前发一次,改变之后发一次
  • Context参数 传入任意类型的对象,在接收消息回调的代码中科院接收到这个对象,是KVO中的一种传值方式。

 

2.案例操作

2.1 新建项目叫:KVO的基本使用

2.2 demo代码

新增人-age属性,如下:

#import 
NS_ASSUME_NONNULL_BEGIN@interface Person : NSObject@property (nonatomic,assign)int age;@endNS_ASSUME_NONNULL_END#import "Person.h"@implementation Person@end

 

2.3 在ViewController实现

2.3.1 导入Person类,并创建类对象

2.3.2 在ViewDidLoad中创建类对象,并注册观察者

在下面观察属性值变化实现方法observeValueForKeyPath

最后要移除观察者removeObserver

我们加入响应事件touchesBegan,每次点击页面,age都会自动加1

下面是整个的代码ViewController的代码

#import "ViewController.h"#import "Person.h"@interface ViewController ()@property(nonatomic,strong) Person * p;@end@implementation ViewController- (void)viewDidLoad {    [super viewDidLoad];        _p = [[Person alloc]init];        //注册    [_p addObserver:self forKeyPath:NSStringFromSelector(@selector(age)) options:(NSKeyValueObservingOptionNew) context:nil];}- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary
*)change context:(void *)context{ NSLog(@"%@",change);}- (void)touchesBegan:(NSSet
*)touches withEvent:(UIEvent *)event{ static int a; _p.age = a++;}- (void)dealloc{ [_p removeObserver:self forKeyPath:NSStringFromSelector(@selector(age))];}@end

 

2.4 代码测试

点击了界面三次,打印结果如下:

发现new值在不断地增加,满足了监听person的age属性的要求。

 

>>>>拓展

上面代码[p addObserver:self],而在控制器中声明p对象用的属性修饰词是Strong,这其中中这里面有没有强引用关系?(p有没有强引用self)

在调用addObserver方法后,KVO并不会对观察者进行强引用,所以我们要注意观察者的生命周期,因为[p addObserver:self]中,一旦self(控制器)销毁的时候,p也就是拿不到self,那么剩下一个问题,p会不会调用observeValueForKeyPath呢,答案是仍然是会调用,而给已经释放的内存发送一个消息,接下来会发生crash。

所以要在dealloc方法中,移除观察者。

 

二、KVO的触发模式

KVO在属性发生改变的时候调用是自动的,如果想要手动控制这个调用时机,或者自己实现KVO属性的调用,则可以通过KVO提供的方法进行调用。

如果想手动控制,可以实现下面方法

当我们再次点击屏幕,发现控制台无任何的打印结果。如果想要监听结果,需要在响应事件中加入willChangeValueForKey和didChangeValueForKey方法,如下图

经过加入方法之后,控制台重新出现打印结果如下

 

>>>>拓展

如果没有改变age属性的值,还能触发KVO嘛,也就是注释掉_p.age = a++;(去掉setter方法)还能触发嘛?

我们运行代码,发现还会执行(只要实现了willChangeValueForKey和didChangeValueForKey方法)

 

三、KVO的属性依赖

3.1 案例分析

如果新增一个类Dog,而Dog也新增一属性age,如下图

同时将Dog类作为Person的一个对象

Person并初始化Dog对象

在ViewController中,注册监听者不能使用NSStringFromSelector方法了,应该使用字符串了

然后点击屏幕进行触摸事件点击

在控制台进行打印结果如下:

3.2 案例拓展

新增一需求,如果Dog类属性特别多,我们有一个需求,只要Dog类的任一属性发生改变,就通知Dog类的观察者?

在Dog类中加入level属性,只要Dog类的属性发生改变,通知观察者

Dog类新增属性level等级

而在KVO本身的封装代码中,有一个方法

+ (NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key

NSSet是一个集合,返回所有的属性,方法如下:

 

四、KVO原理探究

对于KVO的原理探究的文章,本人已经写好了,请看一下博客

 

五、自定义KVO

下面我们自己写KVO,在写之前,我们首先看一下苹果自身怎么实现的?比较关键的一个方法是

- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;

如果查看源码发现,苹果是建立NSObject分类Category来实现KVO的 

下面我们就自定义KVO。

5.1 创建分类XY_KVO

5.1.1 自定义一个方法:

- (void)XY_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;

5.1.2 实现方法

- (void)XY_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context{        //1.创建一个类    NSString *oldClassName = NSStringFromClass(self.class);    NSString *newClassName = [@"XYKVO" stringByAppendingString:oldClassName];        /**myClass的父类是person类*/    Class myClass = objc_allocateClassPair(self.class, newClassName.UTF8String, 0);    //注册类    objc_registerClassPair(myClass);        //2.重写set方法(所谓的重写是添加set方法,如果不重写set方法,子类是没有set方法的(父类是Person类),但子类是可以调用set方法的)//    class_addMethod(<#Class  _Nullable __unsafe_unretained cls#>, <#SEL  _Nonnull name#>, <#IMP  _Nonnull imp#>, <#const char * _Nullable types#>)    /**     *Class 给那个类添加方法     *SEL 方法编号     *IMP 方法实现     *types 返回值类型     */        /**     v@:@代表返回值为void,第一个参数@代表调用者,第二个:代表方法编号也就是方法名字,第三个代表传参(真正代表你传参的)     */    class_addMethod(myClass, @selector(setAge:), (IMP)setAge, "v@:@");            //3.修改isa指针!!isa指针指向子类    object_setClass(self, myClass);    }

void setAge(id self,SEL _cmd,NSString *age){

    NSLog(@"来了");

}

拓展》》》

class_addMethod(myClass, @selector(setAge:), (IMP)setAge, "v@:@");中setAge:明明只有一个参数,为什么返回的要有三个参数如果不写,就会返回对象原因?

举例:

_p = [Person alloc];

_p = [_p init];

将init这句代码改为_p = objc_msgSend(_p,@selector(init))

(任何oc的方法调用都会变为objc_msgSend(_p,@selector(方法)))-消息发送,第一个参数是方法对象,第二个方法编号名字

 

5.1.3 测试过程

1.导入类NSObject+XYKVO.h
2.使用自定义的KVO

3.触碰时改变age值

4.然后查看自定义KVO里面set方法,看是否打印出“来了”

结果出现“来了”,说明自定义KVO实现监听啦

 

5.3 监听属性

5.3.1 将观察者保存到当前对象

5.3.2 发送监听通知

void setAge(id self,SEL _cmd,NSString *age){    NSLog(@"来了");    //调用父类的set方法    Class class = [self class];//子类当前类型   object_setClass(self, class.getSuperclass(class));    objc_msgSend(self,@selector(setAge:),age)//    //拿到观察者之后,要发送通知    id observer = objc_getAssociatedObject(self, @"observer");    if (observer) {       objc_msgSend(observer,@selector(observeValueForKeyPath:ofObject:change:context:),@"age",self,@{
@"new":age,@"kind":@1},nil); } object_setClass(self, class);}

通过objc_msgSend(observer,@selector(observeValueForKeyPath:ofObject:change:context:),@"age",self,@{@"new":age,@"kind":@1},nil);就可以实现监听在控制器中

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary
*)change context:(void *)context{ NSLog(@"%@",change);}

 

六、 KVO对容器类的监听

6.1 新增容器类属性并初始化

初始化

6.2 对容器类添加监听

6.3 触碰屏幕查看容器属性变化

如果上面的容器使用注释的那行[_p.arr addObject:@"11"],会触发KVO嘛?

答案是不会,因为addObject不是set方法,KVO通过set方法来触发,而苹果专门给KVO提供个接口,通过mutableArrayValueForKey方法,来触发

6.4 结果

发现tempArr是NSKeyValueNotifyingMutableArray,这个类是NSMutableArray的子类,我们可以联想到容器类监听和属性监听差不多,有着异曲同工之处。请大家细细体会。

 

上面就是关于KVO的基本讲解,以后会慢慢的剖解更多OC的底层知识,供大家阅读。

转载于:https://www.cnblogs.com/guohai-stronger/p/10272146.html

你可能感兴趣的文章
C#类与结构体究竟谁快——各种函数调用模式速度评测
查看>>
我到底要选择一种什么样的生活方式,度过这一辈子呢:人生自由与职业发展方向(下)...
查看>>
poj 题目分类
查看>>
windows 安装yaml支持和pytest支持等
查看>>
读书笔记:季羡林关于如何做研究学问的心得
查看>>
面向对象的优点
查看>>
套接口和I/O通信
查看>>
阿里巴巴面试之利用两个int值实现读写锁
查看>>
浅谈性能测试
查看>>
Winform 菜单和工具栏控件
查看>>
jequery动态创建form
查看>>
CDH版本大数据集群下搭建的Hue详细启动步骤(图文详解)
查看>>
巧用Win+R
查看>>
浅析原生js模仿addclass和removeclass
查看>>
Python中的greenlet包实现并发编程的入门教程
查看>>
java中遍历属性字段及值(常见方法)
查看>>
深入理解jQuery框架-框架结构
查看>>
YUI3自动加载树实现
查看>>
python知识思维导图
查看>>
当心JavaScript奇葩的逗号表达式
查看>>