一、介绍

什么是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,原理浅析的更多相关文章

  1. 【OC底层】KVO原理

    KVO的原理是什么?底层是如何实现的? KVO是Key-value observing的缩写. KVO是Objective-C是使用观察者设计模式实现的. Apple使用了isa混写(isa-swiz ...

  2. 老生常谈系列之Aop--Spring Aop原理浅析

    老生常谈系列之Aop--Spring Aop原理浅析 概述 上一篇介绍了AspectJ的编译时织入(Complier Time Weaver),其实AspectJ也支持Load Time Weaver ...

  3. KVO原理解析

    KVO在我们项目开发中,经常被用到,但很少会被人关注,但如果面试一些大公司,针对KVO的面试题可能如下: 知道KVO嘛,底层是怎么实现的? 如何动态的生成一个类? 今天我们围绕上面几个问题,我们先看K ...

  4. KVO的使用三:基于runtime实现KVO

    苹果的KVO原理通过isa-swizzling技术实现,本质实现逻辑是在runtime时添加一个子类,重写set方法进行操作,现在我们也基于runtime来实现一个KVO. 首先新建一个Person类 ...

  5. Runtime - 消息发送原理

    Runtime - 消息发送原理. Objective-C运行时的核心就在于消息分派器objc_msgSend,消息分派器把选择器映射为函数指针,并调用被引用的函数. 要想理解objc_msgSend ...

  6. 沉淀,再出发:docker的原理浅析

    沉淀,再出发:docker的原理浅析 一.前言 在我们使用docker的时候,很多情况下我们对于一些概念的理解是停留在名称和用法的地步,如果更进一步理解了docker的本质,我们的技术一定会有质的进步 ...

  7. HTTP长连接和短连接原理浅析

    原文出自:HTTP长连接和短连接原理浅析

  8. Javascript自执行匿名函数(function() { })()的原理浅析

    匿名函数就是没有函数名的函数.这篇文章主要介绍了Javascript自执行匿名函数(function() { })()的原理浅析的相关资料,需要的朋友可以参考下 函数是JavaScript中最灵活的一 ...

  9. [转帖]Git数据存储的原理浅析

    Git数据存储的原理浅析 https://segmentfault.com/a/1190000016320008   写作背景 进来在闲暇的时间里在看一些关系P2P网络的拓扑发现的内容,重点关注了Ma ...

随机推荐

  1. python中使用adb命令的方法

    在python中使用adb命令,可以导入os模块. 1 简单的adb命令. 如:os.system('adb version') 2 稍微复杂的adb命令. 如:os.system('adb shel ...

  2. 一文解读JSON (转)

    JSON作为目前Web主流的数据交换格式,是每个IT技术人员都必须要了解的一种数据交换格式.尤其是在Ajax和REST技术的大行其道的当今,JSON无疑成为了数据交换格式的首选! 今天我们一起来学习一 ...

  3. 【Java基础】接口和抽象类之间的对比

    Java 中的接口和抽象类之间的对比 一.接口 Interface,将其翻译成插座可能就更好理解了.我们通常利用接口来定义实现类的行为,当你将插座上连接笔记本的三角插头拔掉,换成微波炉插上去的时候,你 ...

  4. 任正非讲话稿 PDF整理版

    任正非讲话稿 PDF整理版 任正非思想之路 这里收录了任正非讲话稿400余篇,从1994年到2018年,从深圳.中国到东南亚.非洲.欧洲.美洲,从研发.市场.服务到财经.人力资源.战略.内控与公共关系 ...

  5. Ubuntu系统修改资源为阿里云镜像

    一般都会推荐使用国内的镜像源,比如163或者阿里云的镜像服务器将下列文本添加到/etc/apt/sources.list文件里 deb http://mirrors.aliyun.com/ubuntu ...

  6. dependencyManagement

    maven中的继承是在子类工程中加入父类的groupId,artifactId,version并用parent标签囊括 depenentManagement标签作用: 当父类的pom.xml中没有de ...

  7. 如何将pyqt5的qt-designer设计出来的 .ui 和 .qrc 文件转化成 .py 文件

    一.pyrcc5的使用 1.1 作用 将 .qrc 资源文件转换成py文件,并在主程序中通过 import 引入 1.2 资源文件编写说明 创建一个icon.qrc,代码如下: <RCC> ...

  8. openpyxl基本操作

    参考资料:OpenPyXL的使用教程(一) openpyxl 基本操作 # 创建xml from openpyxl import Workbook # 创建工作簿 wb = Workbook() # ...

  9. acwing 853. 有边数限制的最短路 模板

    地址 https://www.acwing.com/problem/content/description/855/ 给定一个n个点m条边的有向图,图中可能存在重边和自环, 边权可能为负数. 请你求出 ...

  10. SpringBoot条件注解的总结

    https://blog.csdn.net/qq_31142553/article/details/86439950