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

简化成伪代码如下:

  1. 调用willChangeValueForKey:
  2. 调用原来的setter实现
  3. 调用didChangeValueForkey
  4. 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原理解析的更多相关文章

  1. android黑科技系列——Apk的加固(加壳)原理解析和实现

    一.前言 今天又到周末了,憋了好久又要出博客了,今天来介绍一下Android中的如何对Apk进行加固的原理.现阶段.我们知道Android中的反编译工作越来越让人操作熟练,我们辛苦的开发出一个apk, ...

  2. [原][Docker]特性与原理解析

    Docker特性与原理解析 文章假设你已经熟悉了Docker的基本命令和基本知识 首先看看Docker提供了哪些特性: 交互式Shell:Docker可以分配一个虚拟终端并关联到任何容器的标准输入上, ...

  3. 【算法】(查找你附近的人) GeoHash核心原理解析及代码实现

    本文地址 原文地址 分享提纲: 0. 引子 1. 感性认识GeoHash 2. GeoHash算法的步骤 3. GeoHash Base32编码长度与精度 4. GeoHash算法 5. 使用注意点( ...

  4. Web APi之过滤器执行过程原理解析【二】(十一)

    前言 上一节我们详细讲解了过滤器的创建过程以及粗略的介绍了五种过滤器,用此五种过滤器对实现对执行Action方法各个时期的拦截非常重要.这一节我们简单将讲述在Action方法上.控制器上.全局上以及授 ...

  5. Web APi之过滤器创建过程原理解析【一】(十)

    前言 Web API的简单流程就是从请求到执行到Action并最终作出响应,但是在这个过程有一把[筛子],那就是过滤器Filter,在从请求到Action这整个流程中使用Filter来进行相应的处理从 ...

  6. GeoHash原理解析

    GeoHash 核心原理解析       引子 一提到索引,大家脑子里马上浮现出B树索引,因为大量的数据库(如MySQL.oracle.PostgreSQL等)都在使用B树.B树索引本质上是对索引字段 ...

  7. alibaba-dexposed 原理解析

    alibaba-dexposed 原理解析 使用参考地址: http://blog.csdn.net/qxs965266509/article/details/49821413 原理参考地址: htt ...

  8. 支付宝Andfix 原理解析

    支付宝Andfix 原理解析 使用参考地址: http://blog.csdn.net/qxs965266509/article/details/49802429 原理参考地址: http://blo ...

  9. JavaScript 模板引擎实现原理解析

    1.入门实例 首先我们来看一个简单模板: <script type="template" id="template"> <h2> < ...

随机推荐

  1. async与defer

    <script>元素的几种常见属性: async  异步加载,立即下载,不应妨碍页面其他操作,标记为 async 的异步脚本并不保证按照指定的先后顺序执行,因此异步脚本不应该在加载期间修改 ...

  2. 快递100码json

    { "AOL澳通速递": "aolau", "A2U速递": "a2u", "AAE快递": &qu ...

  3. LeetCode 单链表专题 (一)

    目录 LeetCode 单链表专题 <c++> \([2]\) Add Two Numbers \([92]\) Reverse Linked List II \([86]\) Parti ...

  4. Linux用命令启动程序(eclipse、IDEA等)

    打开根目录用管理员权限打开HOME 找到下图截图中的框选出的文件 用文本编辑器打开后 在文件末尾添加所需要打开的应用文件所在的目录 这里以本人的IDEA和eclipse为例:

  5. scala Actor Akka

    推荐博客:过往记忆 https://www.iteblog.com/archives/1154.html akka.io

  6. python从入门到实践-7章用户输入和while循环

    #!/user/bin/env python# -*- coding:utf-8 -*- # input() 可以让程序暂停工作# int(input('please input something: ...

  7. Ubuntu环境下配置darknet

    本教程基于Linux物理机进行相关配置,要求物理机中包含N卡且Capbility>=3.0,小于3.0(Fermi架构)只允许配置cuda,不能配置使用Cudnn: 本教程分为: 1.安装NVI ...

  8. Three.js学习笔记01

    1.四大组件: 场景:场景是所有物体的容器 var scene = new THREE.Scene(); 相机: 正投影相机:远处的和近处的是一样大 THREE.OrthographicCamera ...

  9. 电子科技大学实验中学PK赛(二)比赛题解

    比赛地址:http://qscoj.cn/contest/27/ A题 FIFA强化 分析:这个题目要求说的比较明显,用几个if判断一下就好了.不要一判断完就输出,最好用一个ans储存下答案.输出答案 ...

  10. 【安富莱专题教程第2期】uC/Probe简易使用说明,含MDK和IAR,支持F103,F407和F429开发板

    说明:1. 在uCOS工程调试时,这个软件还是非常给力的,方便查看各种信息,可以认为是MDK或者IAR调试功能的图形化版本,使用JLINK连接可以随时连接查看,无需目标端代码.2. 当前教程中,我们使 ...