KVO原理解析
KVO在我们项目开发中,经常被用到,但很少会被人关注,但如果面试一些大公司,针对KVO的面试题可能如下:
- 知道KVO嘛,底层是怎么实现的?
- 如何动态的生成一个类?
今天我们围绕上面几个问题,我们先看KVO底层实现原理,以及怎么自己写一个KVO?
一、KVO
1. KVO定义
KVO:可以监听一个对象的某个属性是否发生了改变,或者通知其他对象的指定属性发生了改变。
2.KVO实现
2.1 监听某个对象的属性
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
2.2 实现协议
- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary *)change context:(nullable void *)context;
2.3 移除监听
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
下面是一个简单的演示:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib. self.person = [[ZJPerson alloc] init]; [self.person setName:@"zhangsan"]; [self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil]; } - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
[self.person setName:@"lisi"];
} - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
NSLog(@"%@", change);
} - (void)dealloc{
[self.person removeObserver:self forKeyPath:@"name"];
}
运行结果
通过以上demo,我们来思考KVO为什么能监听到属性变化,底层又是怎么样实现的呢?
3. KVO底层实现
KVO是通过isa-swizzling技术实现的。运行时根据原类创建一个中间类,这个中间类是原类的子类,并动态修改当前对象的isa指针指向中间类,并且将class 方法重写,返回原类的class。苹果建议通过class 实例方法来获取对象类型。
在查看KVO底层实现,我们首先用runtime在添加监听之前以及之后的类对象
NSLog(@"%@", object_getClass(self.person));
[self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
NSLog(@"%@", object_getClass(self.person));
可以查看结果如下:
-- ::18.726028+ KVO[:] ZJPerson
-- ::18.726535+ KVO[:] NSKVONotifying_ZJPerson
通过上面发现,添加监听之后,实例对象的类对象发生了改变,系统自动为我们动态添加了一个NSKVONotifying_+类名的类,改变属性的值是通过setter方法进行实现,很明显是系统已经动态生成了NSKVONotifying_ZJPerson类,并重写了setter方法,新创建的NSKVONotifying_ZJPerson是ZJPerson的子类。所以不可以创建NSKVONotifying_ZJPerson类了,如果创建了NSKVONotifying_ZJPerson类,会报以下错误:
-- ::32.223288+ KVO[:] [general] KVO failed to allocate class pair for name NSKVONotifying_ZJPerson, automatic key-value observing will not work for this class
错误提示的是:创建NSKVONotifying_ZJPerson失败。
那么问题又来了,重写的setter方法内部又做了什么?我们再次利用runtime打印下面方法的实现。
通过上面发现,发现内部调用了Foundation框架的_NSSetObjectValueAndNotify方法,我们再次看看_NSSetObjectValueAndNotify内部的实现过程如下:
. `-[NSObject(NSKeyValueObservingPrivate) _changeValueForKey:key:key:usingBlock:]:
. -[NSObject(NSKeyValueObservingPrivate) _changeValueForKeys:count:maybeOldValuesDict:usingBlock:]:
. [ZJPerson setName:];
. `NSKeyValueDidChange:
. `NSKeyValueNotifyObserver:
. - (void)observeValueForKeyPath:ofObject:change:context
简化成伪代码如下:
- 调用willChangeValueForKey:
- 调用原来的setter实现
- 调用didChangeValueForkey
- didChangeValueForkey: 内部会调用observer的observeValueForKeyPath方法
- (void)setName:(NSString *)name{
_NSSetObjectValueAndNotify();
} void _NSSetObjectValueAndNotify {
6 [self willChangeValueForKey:@"name"];
7 [super setName:name];
8 [self didChangeValueForKey:@"name"];
} - (void)didChangeValueForKey:(NSString *)key{
[observe observeValueForKeyPath:key ofObject:self change:nil context:nil];
}
拓展》〉》NSKVONotifying_ZJPerson内部重写了方法?
利用runtime打印方法列表
unsigned int count;
Method *methods = class_copyMethodList(object_getClass(self.person), &count); for (NSInteger index = ; index < count; index++) {
Method method = methods[index]; NSString *methodStr = NSStringFromSelector(method_getName(method)); NSLog(@"%@\n", methodStr);
}
打印结果
-- ::07.883400+ KVO[:] setName:
-- ::07.883571+ KVO[:] class
-- ::07.883676+ KVO[:] dealloc
-- ::07.883793+ KVO[:] _isKVOA
发现除了重写了setName: 还重写了class dealloc _isKVOA等方法
调用class方法可能里面实现是:
- (Class) class {
return [ZXYPerson class]
}
而不是NSKVONotifying_ZJPerson类,为了屏蔽了内部实现,隐藏了该类,如果想查看可以通过runtime的object_getClass()方法获取真实运行时的情况
二、如何动态生成类
说到动态生成一个类,也就是利用了苹果的runtime机制,下面我们来动态创建生成类。
2.1 创建类
Class customClass = objc_allocateClassPair([NSObject class], "ZJCustomClass", );
2.2 添加实例变量
// 添加实例变量
class_addIvar(customClass, "age", sizeof(int), , "i");
2.3 添加方法,V@:表示方法的参数和返回值
class_addMethod(customClass, @selector(hahahha), (IMP)hahahha, "V@:");
需要实现的方法:
void hahahha(id self, SEL _cmd)
{
NSLog(@"hahahha====");
} - (void)hahahha{
}
然后注册到运行时环境
objc_registerClassPair(customClass);
下面是打印方法列表以及成员变量列表
#pragma mark - Util - (NSString *)copyMethodsByClass:(Class)cls{
unsigned int count;
Method *methods = class_copyMethodList(cls, &count); NSString *methodStrs = @""; for (NSInteger index = ; index < count; index++) {
Method method = methods[index]; NSString *methodStr = NSStringFromSelector(method_getName(method)); methodStrs = [NSString stringWithFormat:@"%@ ", methodStr];
} free(methods); return methodStrs;
} - (NSString *)copyIvarsByClass:(Class)cls{
unsigned int count;
Ivar *ivars = class_copyIvarList(cls, &count); NSMutableString *ivarStrs = [NSMutableString string]; for (NSInteger index = ; index < count; index++) {
Ivar ivar = ivars[index]; NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)]; //获取成员变量的名字 NSString *ivarType = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)]; //获取成员变量的数据类型 [ivarStrs appendString:@"\n"];
[ivarStrs appendString:ivarName];
[ivarStrs appendString:@"-"];
[ivarStrs appendString:ivarType]; } free(ivars); return ivarStrs;
}
如果想要了解更多的KVO,可以关注更新的博客
https://www.cnblogs.com/guohai-stronger/p/10272146.html
以上就是KVO的基本内容,希望通过本篇博客,大家对KVO原理以及基本使用有更深的了解!!!
KVO原理解析的更多相关文章
- android黑科技系列——Apk的加固(加壳)原理解析和实现
一.前言 今天又到周末了,憋了好久又要出博客了,今天来介绍一下Android中的如何对Apk进行加固的原理.现阶段.我们知道Android中的反编译工作越来越让人操作熟练,我们辛苦的开发出一个apk, ...
- [原][Docker]特性与原理解析
Docker特性与原理解析 文章假设你已经熟悉了Docker的基本命令和基本知识 首先看看Docker提供了哪些特性: 交互式Shell:Docker可以分配一个虚拟终端并关联到任何容器的标准输入上, ...
- 【算法】(查找你附近的人) GeoHash核心原理解析及代码实现
本文地址 原文地址 分享提纲: 0. 引子 1. 感性认识GeoHash 2. GeoHash算法的步骤 3. GeoHash Base32编码长度与精度 4. GeoHash算法 5. 使用注意点( ...
- Web APi之过滤器执行过程原理解析【二】(十一)
前言 上一节我们详细讲解了过滤器的创建过程以及粗略的介绍了五种过滤器,用此五种过滤器对实现对执行Action方法各个时期的拦截非常重要.这一节我们简单将讲述在Action方法上.控制器上.全局上以及授 ...
- Web APi之过滤器创建过程原理解析【一】(十)
前言 Web API的简单流程就是从请求到执行到Action并最终作出响应,但是在这个过程有一把[筛子],那就是过滤器Filter,在从请求到Action这整个流程中使用Filter来进行相应的处理从 ...
- GeoHash原理解析
GeoHash 核心原理解析 引子 一提到索引,大家脑子里马上浮现出B树索引,因为大量的数据库(如MySQL.oracle.PostgreSQL等)都在使用B树.B树索引本质上是对索引字段 ...
- alibaba-dexposed 原理解析
alibaba-dexposed 原理解析 使用参考地址: http://blog.csdn.net/qxs965266509/article/details/49821413 原理参考地址: htt ...
- 支付宝Andfix 原理解析
支付宝Andfix 原理解析 使用参考地址: http://blog.csdn.net/qxs965266509/article/details/49802429 原理参考地址: http://blo ...
- JavaScript 模板引擎实现原理解析
1.入门实例 首先我们来看一个简单模板: <script type="template" id="template"> <h2> < ...
随机推荐
- 小程序重新封装打印函数console.log
习惯性使用console.log打印获取到的数据,信息等,然后上星期大佬看见了说怎么那么多打印信息出来,线上那个也是吗?问我能不能线上的就不打印出来? 我就说那就封装一个打印函数呗. 重写一个没问题, ...
- The SQL Server instance returned an invalid or unsupported protocol version during login negotiatio
在使用.net core 连接sqlserver的时候遇到了这个问题 从字面意思理解大致是个什么版本不支持, 谷歌一下吧,ok,看到这个2000我就知道什么问题了 我的数据库还是2000的,总算把20 ...
- ES6新增的常用数组方法(forEach,map,filter,every,some)
ES6新增的常用数组方法 let arr = [1, 2, 3, 2, 1]; 一 forEach => 遍历数组 arr.forEach((v, i) => { console.log( ...
- h5直接分享的实现方案
首先得知道,h5是无法直接通过js跳转到微信或QQ等软件进行分享, 参照新浪的分享方式,在uc浏览器和QQ浏览器等主流浏览器中是可以直接分享的, 原因是uc浏览器和QQ浏览器这样的主流浏览器是自带分享 ...
- centos7zabbix-agen安装
安装包下载地址:http://www.zabbix.com/download.php 下载对应rpm包 http://repo.zabbix.com/zabbix/ wget http://r ...
- mysql数据库连接异常问题(总结)
mysql数据库连接异常问题(总结) 1.1 前言 最近项目由1个数据源增加至了3个数据源(连接池使用C3P0),结果各种奇葩的数据库连接问题接踵而至,为防止将来再次遇到同样的问题不犯同样错误,现 ...
- android利用ContentResolver访问者获取手机联系人信息
转载自:http://www.jb51.net/article/106379.htm 首先需要在AndroidManifest.xml文件中添加权限: <uses-permission andr ...
- PHP全栈从入门到精通1
thinkphp框架,是一堆代码(常量,方法,和类)的集合,框架是一个半成品的应用,还包含一些优秀的设计模式. 框架的使用,代码风格不一样,维护难,项目生命周期短,功能扩展存在局限,好处为,简单,快捷 ...
- Hadoop伪分布式配置
一步一步来: 安装VMWARE简单,安装CentOS也简单 但是,碰到了一个问题:安装的虚拟机没有图形化界面 最后,我选择了CentOS-7-x86_64-DVD-1503-01.iso镜像 配置用户 ...
- [Swift]LeetCode646. 最长数对链 | Maximum Length of Pair Chain
You are given n pairs of numbers. In every pair, the first number is always smaller than the second ...