使用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 ...
随机推荐
- mysql操作进阶
# ### part1 单表查询# sql 查询语句的完整语法 ''' select .. from .. where .. group by .. having .. order by .. lim ...
- mysql在本地已经启动,但是在网页上不能直接访问的解决
1.将mysql文件下的my.ini中的路径代码 # 设置mysql的安装目录 basedir=E:/Develop/mysql # 设置mysql数据库的数据的存放目录 datadir=E:/Dev ...
- 划分为k个相等的子集
给定一个整数数组 nums 和一个正整数 k,找出是否有可能把这个数组分成 k 个非空子集,其总和都相等. 示例 1: 输入: nums = [4, 3, 2, 3, 5, 2, 1], k = 4 ...
- 04-Uwsgi配置启动Nginx虚拟主机配置
一.虚拟环境的安装 1.安装虚拟环境 sudo pip3 install virtualenv 2.安装virtualenvwrapper sudo pip3 install virtualenvwr ...
- Java题库——Chapter12 异常处理和文本IO
异常处理 1)What is displayed on the console when running the following program? class Test { public stat ...
- .NET轻松实现支付宝服务窗网页授权并获取用户相关信息
前言: 最近在开发一个商业街区的聚合扫码支付功能,其中需要用到的有支付宝,微信两种支付方式,当然对于开发微信支付而已作为自己的老本行已经比较熟悉了,然而对于我来说支付宝支付还是头一次涉及到.这次项目中 ...
- 发布方式一:发布.netcore流程
通过注册世纪互联,发布网站 注册需要上传身份证正反照片,以及打款一元,试用期一个月 https://www.azure.cn 注册完成后,进入主页面: 如图所示,创建完成后的效果: 点开详情, ...
- PHP实现支付宝登录
1.支付宝路由拼接 public function dev() { $http = 'https://openauth.alipaydev.com/oauth2/publicAppAuthorize. ...
- python-Redis模块常用的方法汇总
Redes模块常用的方法汇总 一.创建建Redis对象 1.直接使用 import redis r = redis.Redis(host='127.0.0.1', port=6379) 2.连接池使用 ...
- Java - java概述
简介: JAVA是一门面向对象的编程语言 1995有sun公司发布 java程序执行流程: xxxjava源文件, 经过编译器编译 产生字节码文件 字节码交给解释器 解释成当前平台的本地机器指令 名词 ...