使用Runtime自定义KVO,原理浅析
一、介绍
什么是KVO?全称key-value-observer,键值观察,观察者设计模式的另一种实现。其作用是通过观察者监听属性值的变化而做出函数回调。
二、原理
KVO基于Runtime机制实现,使用了isa的混写技术
监听者监听类A的某一个属性的变化,系统会动态为类A创建一个子类NSKVONotifying_A,并将类A的isa指针重新指向该子类
系统会重写类A的setter方法。( 赋值前后分别调用willChangeValueForKey和didChangeValueForKey跟踪新旧值 )
当类A的属性发生改变时,系统通知监听者,调用observeValueForKey:ofObject:change:context方法即可
三、图示
三、基本实现
Student类
// Created by 夏远全 on 2019/10/12. #import <Foundation/Foundation.h> NS_ASSUME_NONNULL_BEGIN @interface Student : NSObject
@property (nonatomic, copy) NSString *name;
@end
ViewController类
-(void)test_objc_KVO { //创建对象
self.stu = [[Student alloc] init];
self.stu.name = @"张三"; //注册观察者
[self.stu addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:NULL]; //修改值
self.stu.name = @"李四";
} -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context { NSLog(@"被观测对象:%@, 被观测的属性:%@, 值的改变: %@\n", object, keyPath, change); }
断点结果:监听前后,stu对象的类类型发生了变化,Student -> NSKVONotifying_Student
打印结果
-- ::01.251412+ 运行时[:] 被观测对象:<Student: 0x600003d9f3c0>, 被观测的属性:name, 值的改变: {
kind = ;
new = "\U674e\U56db";
}
四、自定义
(1)思想:
- 添加监听方法
- 创建一个子类
- 改写父类isa指针
- 关联观察者
- 重写setter方法
- 给父类发送setter消息
- 给观察者发送observeValueForKeyPath:ofObject:change:context:消息
(2)实现
Student类
// Created by 夏远全 on 2019/10/12. #import <Foundation/Foundation.h> NS_ASSUME_NONNULL_BEGIN @interface Student : NSObject
@property (nonatomic, copy) NSString *name; ///自定义的监听方法
-(void)xyq_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context; @end NS_ASSUME_NONNULL_END
//
// Student.m
// 运行时
//
// Created by 夏远全 on 2019/10/12.
// #import "Student.h"
#import <objc/message.h> @implementation Student void setterMethod(id self, SEL _cmd, NSString *name) { //5、调用父类的方法
struct objc_super superClass = {
self,
class_getSuperclass([self class])
};
objc_msgSendSuper(&superClass, _cmd, name); //6、通知观察者调用observeValueForKeyPath:ofObject:change:context:
id observer = objc_getAssociatedObject(self, (__bridge const void *)@"observer");
NSString *methodName = NSStringFromSelector(_cmd);
NSString *key = getValueKey(methodName);
objc_msgSend(observer, @selector(observeValueForKeyPath:ofObject:change:context:), key, self, @{key:name}, nil); } /// 通过setter方法截取属性名
NSString *getValueKey(NSString *setter){ //去掉set
if ([setter hasPrefix:@"set"]) {
setter = [setter stringByReplacingOccurrencesOfString:@"set" withString:@""];
}
//去掉:
if ([setter hasSuffix:@":"]) {
setter = [setter stringByReplacingOccurrencesOfString:@":" withString:@""];
}
//首字母小写
NSString *key = [setter stringByReplacingOccurrencesOfString:[setter substringToIndex:] withString:[[setter substringToIndex:] lowercaseString] options: range:NSMakeRange(,)];
return key;
} - (void)xyq_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context { //1、生成子类
const char * clazz = class_getName([self class]);
NSString *className = [NSString stringWithCString:clazz encoding:NSUTF8StringEncoding];
NSString *subClassName = [NSString stringWithFormat:@"NSKVONotifying_%@",className];
Class subClass = objc_getClass(subClassName.UTF8String);
subClass = objc_allocateClassPair([self class], [subClassName UTF8String], );
objc_registerClassPair(subClass); //2、isa指向子类
object_setClass(self, subClass); //3、关联观察者
objc_setAssociatedObject(self, (__bridge const void *)@"observer", observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC); //4、重写set方法
NSString *setNameStr = [NSString stringWithFormat:@"set%@:",keyPath.capitalizedString];
SEL setSelector = NSSelectorFromString(setNameStr);
class_addMethod(subClass, setSelector, (IMP)setterMethod, "v@:@");
} @end
ViewController类
-(void)test_objc_KVO { //创建对象
self.stu = [[Student alloc] init];
self.stu.name = @"张三"; //注册观察者
[self.stu xyq_addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:NULL]; //修改值
self.stu.name = @"李四";
} -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context { NSLog(@"被观测对象:%@, 被观测的属性:%@, 值的改变: %@\n", object, keyPath, change); }
断点结果:监听前后,stu对象的类类型发生了变化,Student -> NSKVONotifying_Student
打印结果
-- ::17.929772+ 运行时[:] 被观测对象:<NSKVONotifying_Student: 0x6000002e0420>, 被观测的属性:name, 值的改变: {
name = "\U674e\U56db";
}
五、扩展
这个案例只是浅浅的探究了一下实现原理,其他这个还有更大的应用。
我们可以给NSObject创建一个分类NSObject(KVO),实现各种属性的监听实现。
具体操作自己动手去吧,不在本文做演示。
使用Runtime自定义KVO,原理浅析的更多相关文章
- 【OC底层】KVO原理
KVO的原理是什么?底层是如何实现的? KVO是Key-value observing的缩写. KVO是Objective-C是使用观察者设计模式实现的. Apple使用了isa混写(isa-swiz ...
- 老生常谈系列之Aop--Spring Aop原理浅析
老生常谈系列之Aop--Spring Aop原理浅析 概述 上一篇介绍了AspectJ的编译时织入(Complier Time Weaver),其实AspectJ也支持Load Time Weaver ...
- KVO原理解析
KVO在我们项目开发中,经常被用到,但很少会被人关注,但如果面试一些大公司,针对KVO的面试题可能如下: 知道KVO嘛,底层是怎么实现的? 如何动态的生成一个类? 今天我们围绕上面几个问题,我们先看K ...
- KVO的使用三:基于runtime实现KVO
苹果的KVO原理通过isa-swizzling技术实现,本质实现逻辑是在runtime时添加一个子类,重写set方法进行操作,现在我们也基于runtime来实现一个KVO. 首先新建一个Person类 ...
- Runtime - 消息发送原理
Runtime - 消息发送原理. Objective-C运行时的核心就在于消息分派器objc_msgSend,消息分派器把选择器映射为函数指针,并调用被引用的函数. 要想理解objc_msgSend ...
- 沉淀,再出发:docker的原理浅析
沉淀,再出发:docker的原理浅析 一.前言 在我们使用docker的时候,很多情况下我们对于一些概念的理解是停留在名称和用法的地步,如果更进一步理解了docker的本质,我们的技术一定会有质的进步 ...
- HTTP长连接和短连接原理浅析
原文出自:HTTP长连接和短连接原理浅析
- Javascript自执行匿名函数(function() { })()的原理浅析
匿名函数就是没有函数名的函数.这篇文章主要介绍了Javascript自执行匿名函数(function() { })()的原理浅析的相关资料,需要的朋友可以参考下 函数是JavaScript中最灵活的一 ...
- [转帖]Git数据存储的原理浅析
Git数据存储的原理浅析 https://segmentfault.com/a/1190000016320008 写作背景 进来在闲暇的时间里在看一些关系P2P网络的拓扑发现的内容,重点关注了Ma ...
随机推荐
- python中使用adb命令的方法
在python中使用adb命令,可以导入os模块. 1 简单的adb命令. 如:os.system('adb version') 2 稍微复杂的adb命令. 如:os.system('adb shel ...
- 一文解读JSON (转)
JSON作为目前Web主流的数据交换格式,是每个IT技术人员都必须要了解的一种数据交换格式.尤其是在Ajax和REST技术的大行其道的当今,JSON无疑成为了数据交换格式的首选! 今天我们一起来学习一 ...
- 【Java基础】接口和抽象类之间的对比
Java 中的接口和抽象类之间的对比 一.接口 Interface,将其翻译成插座可能就更好理解了.我们通常利用接口来定义实现类的行为,当你将插座上连接笔记本的三角插头拔掉,换成微波炉插上去的时候,你 ...
- 任正非讲话稿 PDF整理版
任正非讲话稿 PDF整理版 任正非思想之路 这里收录了任正非讲话稿400余篇,从1994年到2018年,从深圳.中国到东南亚.非洲.欧洲.美洲,从研发.市场.服务到财经.人力资源.战略.内控与公共关系 ...
- Ubuntu系统修改资源为阿里云镜像
一般都会推荐使用国内的镜像源,比如163或者阿里云的镜像服务器将下列文本添加到/etc/apt/sources.list文件里 deb http://mirrors.aliyun.com/ubuntu ...
- dependencyManagement
maven中的继承是在子类工程中加入父类的groupId,artifactId,version并用parent标签囊括 depenentManagement标签作用: 当父类的pom.xml中没有de ...
- 如何将pyqt5的qt-designer设计出来的 .ui 和 .qrc 文件转化成 .py 文件
一.pyrcc5的使用 1.1 作用 将 .qrc 资源文件转换成py文件,并在主程序中通过 import 引入 1.2 资源文件编写说明 创建一个icon.qrc,代码如下: <RCC> ...
- openpyxl基本操作
参考资料:OpenPyXL的使用教程(一) openpyxl 基本操作 # 创建xml from openpyxl import Workbook # 创建工作簿 wb = Workbook() # ...
- acwing 853. 有边数限制的最短路 模板
地址 https://www.acwing.com/problem/content/description/855/ 给定一个n个点m条边的有向图,图中可能存在重边和自环, 边权可能为负数. 请你求出 ...
- SpringBoot条件注解的总结
https://blog.csdn.net/qq_31142553/article/details/86439950