KVO讲解
最近一直在写swift项目,没有时间更新自己的技术博客,以前在博客里面写过KVO的底层原理,今天我们来看一下KVO的整个使用过程和使用场景(附有demo),大约花大家10-15分钟时间,希望大家看完博客之后对KVO的使用有更清醒的认识。
下面我们按照以下提纲讲解KVO。
- KVO的基本使用
- KVO的触发模式
- KVO的属性依赖
- KVO的原理探究
- 自定义KVO
- KVO对容器类的监听
一、KVO的基本使用
1.基本步骤
- 通过addObserver:forKeyPath:options:context:注册观察者,观察者可以接收keyPath属性的变化事件。
- 在观察者中实现observeValueForKeyPath:ofObject:change:context方法,当keyPath属性发生改变后,KVO会回调这个方法来通知观察者。
- 当观察者不需要监听时,可以调用removeObserve:forKeyPath方法将KVO移除,需要注意的是,调用removeObserve需要在观察者消失之前,否则会导致Crash。
在注册观察者时,可以传入下列参数:
- Observer参数,观察者对象
- keyPath参数 需要观察者的属性,由于是字符串形式,如果传错格式,容易导致Crash。一旦利用系统的反射机制NSStringFromSelector(keyPath)
- options参数 参数是一个枚举类型
- NSKeyValueObserveOptionNew 接收新值,默认为只接受新值
- NSKeyValueObserveOptionOld 接收旧值
- NSKeyValueObserveOptionInitial 在注册时接收一次回调,在改变时也会发送通知
- NSKeyValueObserveOptionPrior 改变之前发一次,改变之后发一次
- Context参数 传入任意类型的对象,在接收消息回调的代码中科院接收到这个对象,是KVO中的一种传值方式。
2.案例操作
2.1 新建项目叫:KVO的基本使用

2.2 demo代码
新增人-age属性,如下:
#import <Foundation/Foundation.h> NS_ASSUME_NONNULL_BEGIN @interface Person : NSObject
@property (nonatomic,assign)int age; @end NS_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<NSKeyValueChangeKey,id> *)change context:(void *)context{
NSLog(@"%@",change);
} - (void)touchesBegan:(NSSet<UITouch *> *)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么?(上面注释的改为_p->_age)
答案是不会触发,因为触发KVO关键是set方法的重写,修改成员变量的值并不会触发set,所以不会触发KVO!(手动触发也可以,通过上面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的原理探究的文章,本人已经写好了,请看一下博客
https://www.cnblogs.com/guohai-stronger/p/9473551.html
五、自定义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, );
//注册类
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":@},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<NSKeyValueChangeKey,id> *)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的底层知识,供大家阅读。
KVO讲解的更多相关文章
- iOS-KVC和KVO精炼讲解(干货)
一.KVO介绍 KVO就是观察者模式,说白了就是你关心的一个值改变了,你就会得到通知.你就可以在你想处理的地方处理这个值. 二.KVO的使用 一般分为三步: 注册监听 使用方法: /** * 添加KV ...
- 深入剖析通知中心和KVO
深入剖析通知中心和KVO 要先了解KVO和通知中心,就得先说说观察者模式,那么观察者模式到底是什么呢?下面来详细介绍什么是观察者模式. 观察者模式 -A对B的变化感兴趣,就注册成为B的观察者,当B发生 ...
- iOS KVC详细讲解
iOS KVC详细讲解 什么是KVC? KVC即NSKeyValueCoding,就是键-值编码的意思.一个非正式的 Protocol,是一种间接访问对象的属性使用字符串来标识属性,而不是通过调用存取 ...
- KVC 与 KVO 理解
KVC 与 KVO 是 Objective C 的关键概念,个人认为必须理解的东西,下面是实例讲解. Key-Value Coding (KVC) KVC,即是指 NSKeyValueCoding,一 ...
- OC键值观察KVO
什么是KVO? 什么是KVO?KVO是Key-Value Observing的简称,翻译成中文就是键值观察.这是iOS支持的一种机制,用来做什么呢?我们在开发应用时经常需要进行通信,比如一个model ...
- KVC 与 KVO 理解-b
KVC 与 KVO 是 Objective C 的关键概念,个人认为必须理解的东西,下面是实例讲解. Key-Value Coding (KVC) KVC,即是指 NSKeyValueCoding,一 ...
- [转] iOS (OC) 中 KVC 与 KVO 理解
转自: http://magicalboy.com/kvc_and_kvo/ KVC 与 KVO 是 Objective C 的关键概念,个人认为必须理解的东西,下面是实例讲解. Key-Value ...
- KVC与KVO理解
转载:https://magicalboy.com/kvc_and_kvo/ KVC 与 KVO 理解 KVC 与 KVO 是 Objective C 的关键概念,个人认为必须理解的东西,下面是实例讲 ...
- 关于KVO导读
入门篇 KVO是什么? Key-value observing is a mechanism that allows objects to be notified of changes to spec ...
随机推荐
- 我的新纪元Day01
在我刚刚开通博客园后,想了好久.不知道第一次随笔该写点什么,想写些自己学到的知识,但技术上还是菜鸟的我完全不知道我能向别人分享什么,想到这里有些沮丧. 但万事开头难,只要我入了门,广阔的编程语言的世界 ...
- python 将os.getcwd()获取路径中的\替换成\\
通过os.getcwd()获取的路径为:D:\Auto\test\mobule,实际需要修改为:D://Auto//test//mobule 代码实现如下: import osb = os.getcw ...
- CountDownLatch和CyclicBarrier 区别
CountDownLatch : 一个线程(或者多个), 等待另外N个线程完成某个事情之后才能执行. CyclicBarrier : N个线程相互等待,任何一个线程完成之前,所有的线程都 ...
- C++的代理类
怎样在一个容器中包含类型不同,但是彼此有关系的对象?众所周知,C++的容器只能存放类型相同的元素,所以直接在一个容器中存储不同类型的对象本身是不可能的,只能通过以下两种方案实现: 1. 提供一个间接层 ...
- 简单工厂模式--java代码实现
简单工厂模式 工厂,生产产品的场所.比如农夫山泉工厂,生产农夫山泉矿泉水.茶π等饮料.矿泉水和茶π都属于饮料,都具有解渴的功能,但是每种饮料给人的感觉是不一样的.矿泉水和茶π在Java中相当于子类,饮 ...
- 金三银四,今年Python就业前,看看这篇文章找找感觉
Python就业行情和前景分析之一爬取数据 最近Python大热,就想要分析一下相关的市场需求,看一下Python到底集中在哪些城市,企业对Python工程师的一些需求到底是怎样的,基于此,爬取了国内 ...
- [区块链\理解BTCD源码]GO语言实现一个区块链原型
摘要 本文构建了一个使用工作量证明机制(POW)的类BTC的区块链.将区块链持久化到一个Bolt数据库中,然后会提供一个简单的命令行接口,用来完成一些与区块链的交互操作.这篇文章目的是希望帮助大家理解 ...
- Quartz.Net学习笔记
一.概述 Quartz.NET是一个强大.开源.轻量的作业调度框架,是 OpenSymphony 的 Quartz API 的.NET移植,用C#改写,可用于winform和asp.net应用中.它灵 ...
- 在个人博客中优雅的使用Gitalk评论插件
在上一篇博客<程序员如何从0到1搭建自己的技术博客>中,我们了解了如何快速的从0到1搭建一个个人博客. 其实细心的你会发现,该博客用到了一个评论插件,这个插件就是Gitalk. 如果想要在 ...
- Fundebug支付宝小程序BUG监控插件更新至0.2.0,新增test()方法,报错增加Page数据
摘要: 0.2.0新增fundebug.test()方法,同时报错增加了Page数据. Fundebug提供专业支付宝小程序BUG监控服务,可以第一时间为您捕获生存环境中小程序的异常.错误或者BUG, ...