KVO底层实现原理,仿写KVO
这篇文章简单介绍苹果的KVO底层是怎么实现的,自己仿照KVO的底层实现,写一个自己的KVO监听
#pragma mark--KVO底层实现
第一步:新建一个Person类继承NSObject
Person.h
#import <Foundation/Foundation.h> @interface Person : NSObject //字符串类型的属性name
@property (nonatomic, strong) NSString *name; @end
Person.m
#import "Person.h" @implementation Person
- (void)setName:(NSString *)name
{ //别问为什么(下面有用处),就是要自己处理set方法
_name = [NSString stringWithFormat:@"%@aaaa",name];
}
@end
第二步:在控制器中创建一个Person类型的对象p,利用苹果的KVO来监听该对象p的name属性的变化
ViewController.h
#import <UIKit/UIKit.h> @interface ViewController : UIViewController @end
ViewController.m
#import "ViewController.h"
#import "Person.h" @interface ViewController () @property (nonatomic, strong) Person *p; @end @implementation ViewController - (void)viewDidLoad
{
[super viewDidLoad]; Person *p = [[Person alloc] init]; // 监听name属性有没有改变
[p addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
self.p = p;
} //点击屏幕修改self.p的name属性的值
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
static int i = ;
i++;
self.p.name = [NSString stringWithFormat:@"%d",i];
} // 所有的KVO监听都会来到这个方法
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context
{
NSLog(@"%@",self.p.name);
} @end
会打印1aaaa,2aaaa,3aaaa...... //从打印可以看出KVO监听的是set方法的调用!!!
实际上:KVO的本质就是监听一个对象有没有调用set方法!!!
怎么验证呢?
1.将Person.h中的代码修改为
#import <Foundation/Foundation.h> @interface Person : NSObject
{
@public
NSString *_name;//为了验证KVO监听的是setter方法
} @end
将ViewController.m的viewDidLoad中的代码修改为
- (void)viewDidLoad
{
[super viewDidLoad]; Person *p = [[Person alloc] init]; // 监听_name有没有改变
[p addObserver:self forKeyPath:@"_name" options:NSKeyValueObservingOptionNew context:nil];
self.p = p;
}
运行后,点击屏幕是没有任何打印的-->结论1:KVO的本质就是监听一个对象有没有调用set方法!!!
第三步:打断点看一下addObserver 到底干了什么事情?

继续走一步:

这个NSKVONotifying_Person类是什么鬼?
估计你已经猜到了! 没错这个NSKVONotifying_Person类就是系统帮我们实现的!!!
怎么验证呢? 想到了吗?我们自己把这个类给重写了!看看会发生什么?
第四步:创建NSKVONotifying_Person类
NSKVONotifying_Person.h
#import <Foundation/Foundation.h> @interface NSKVONotifying_Person : NSObject @end
NSKVONotifying_Person.m
#import "NSKVONotifying_Person.h" @implementation NSKVONotifying_Person @end
第五步运行:

恭喜猜测没错,验证通过!!!
结论2:系统创建了一个NSKVONotifying_XXX的类
结论3:修改了对象p的isa指针
那么系统帮我们创建的NSKVONotifying_XXX类有没有继承自我们自己常见的XXX类呢?
我既然这么问,肯定是继承啦!!因为我们已经把属性对应的set方法给重写了!!!因为已经修改了对象的指针(用户调用对象p的方法就不是Person的方法了,是isa指针指向的类的对应的方法),如果不继承的话,相当于把用户重写的set方法给覆盖了,用户调用自己写的set方法就不起作用了!!!也就有了 结论4:重写对应的set方法再内部实现父类做法,通知观察者
KVO底层实现结论:(都是系统自动帮我们实现的)
1> 创建NSKVONotifying_XXX的类
2> 重写对应属性的set方法,在内部实现父类做法,通知观察者
3> 修改当前对象的isa指针,指向创建的NSKVONotifying_XXX(这样实际调用的时候就会走NSKVONotifying_XXX类中对应的方法)
#pragma mark--仿写KVO实现
知道了KVO底层实现的原理就可以仿照KVO写一个自己的"KVO"了
步骤一:给NSObject写一个分类
NSObject+KVO.h
#import <Foundation/Foundation.h> @interface NSObject (KVO) //添加监听
- (void)ey_addObserver:(NSObject *_Nullable)observer forKeyPath:(NSString *_Nullable)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context; //移除监听
- (void)ey_removeObserver:(NSObject *_Nullable)observer forKeyPath:(NSString *_Nullable)keyPath context:(nullable void *)context; @end
NSObject+KVO.m
//
// NSObject+KVO.m
// KVO底层实现
//
// Created by lieryang on 2017/6/17.
// Copyright © 2017年 lieryang. All rights reserved.
// #import "NSObject+KVO.h"
#import <objc/message.h> static NSString * const EYKVONotifying_ = @"EYKVONotifying_";
static NSString * const observerKey = @"observer";
@implementation NSObject (KVO) //添加监听
- (void)ey_addObserver:(NSObject *_Nullable)observer forKeyPath:(NSString *_Nullable)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context
{
if (keyPath.length == ) {//如果传进来的keyPath为@""或者为nil 直接返回
return;
} // 1. 检查对象的类有没有相应的 setter 方法。
SEL setterSelector = NSSelectorFromString([self setterForGetter:keyPath]); Method setterMethod = class_getInstanceMethod([self class], setterSelector); if (!setterMethod) {//如果没有直接返回,不需要任何处理
NSLog(@"找不到该方法");
return;
} // 2. 检查对象 isa 指向的类是不是一个 KVO 类。如果不是,新建一个继承原来类的子类,并把 isa 指向这个新建的子类
Class clazz = object_getClass(self);
NSString *className = NSStringFromClass(clazz); if (![className hasPrefix:EYKVONotifying_]) {
clazz = [self ey_KVOClassWithOriginalClassName:className];
object_setClass(self, clazz);
} // 3. 为EYKVONotifying_XXX添加setter方法的实现
const char *types = method_getTypeEncoding(setterMethod);
class_addMethod(clazz, setterSelector, (IMP)ey_setter, types); // 4. 添加该观察者到观察者列表中
NSMutableArray *observers = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(observerKey));
if (!observers) {
observers = [NSMutableArray array];
objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(observerKey), observers, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
if ([observers indexOfObject:observer] == NSNotFound) {
[observers addObject:observer];
}
} //移除监听
- (void)ey_removeObserver:(NSObject *_Nullable)observer forKeyPath:(NSString *_Nullable)keyPath context:(nullable void *)context
{ NSMutableArray* observers = objc_getAssociatedObject(self, (__bridge const void *)(observerKey)); [observers removeObject:observer];
}
#pragma mark - 注册自己的EYKVONotifying_XXX
- (Class)ey_KVOClassWithOriginalClassName:(NSString *)className
{
// 生成EYKVONotifying_XXX的类名
NSString *kvoClassName = [EYKVONotifying_ stringByAppendingString:className];
Class kvoClass = NSClassFromString(kvoClassName); // 如果EYKVONotifying_XXX已经被注册过了, 则直接返回
if (kvoClass) {
return kvoClass;
} // 如果EYKVONotifying_XXX不存在, 则创建这个类
Class originClass = object_getClass(self);
kvoClass = objc_allocateClassPair(originClass, kvoClassName.UTF8String, ); // 修改EYKVONotifying_XXX方法的实现, 学习Apple的做法, 隐瞒这个EYKVONotifying_XXX
Method classMethod = class_getInstanceMethod(kvoClass, @selector(class));
const char *types = method_getTypeEncoding(classMethod);
class_addMethod(kvoClass, @selector(class), (IMP)ey_class, types); // 注册EYKVONotifying_XXX
objc_registerClassPair(kvoClass); return kvoClass;
} Class ey_class(id self, SEL cmd)
{
Class clazz = object_getClass(self); // EYKVONotifying_XXX
Class superClazz = class_getSuperclass(clazz); // origin_class
return superClazz; // origin_class
} /**
* 重写setter方法, 新方法在调用原方法后, 通知每个观察者
*/
static void ey_setter(id self, SEL _cmd, id newValue)
{
NSString *setterName = NSStringFromSelector(_cmd);
NSString *getterName = [self getterForSetter:setterName]; if (!getterName) {
NSLog(@"找不到getter方法");
} // 调用原类的setter方法
struct objc_super superClazz = {
.receiver = self,
.super_class = class_getSuperclass(object_getClass(self))
};
// 这里需要做个类型强转, 否则会报too many argument的错误
((void (*)(void *, SEL, id))objc_msgSendSuper)(&superClazz, _cmd, newValue); // 找出观察者的数组
NSArray *observers = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(observerKey));
// 遍历数组
for (id observer in observers) {
// 调用监听者observer的observeValueForKeyPath 方法,因为observer为id类型,所以就偷懒调用了系统的监听回调,要是自己定义方法,会报找方法的错误,可以在添加监听的时候,传进来一个block代码块,在此处回调block,更方便外界的调用
[observer observeValueForKeyPath:getterName ofObject:self change:nil context:nil];
}
} #pragma mark - 生成对应的setter方法字符串
- (NSString *)setterForGetter:(NSString *)key
{
// 1. 首字母转换成大写
NSString * firstString = [[key substringToIndex:] uppercaseString];
// 2. 剩下的字母
NSString * remainingString = [key substringFromIndex:]; // 3. 最前增加set, 最后增加: setName:
NSString *setter = [NSString stringWithFormat:@"set%@%@:", firstString, remainingString]; return setter; }
#pragma mark - 生成对应的getter方法字符串
- (NSString *)getterForSetter:(NSString *)key
{
// setName
if (key.length <= || ![key hasPrefix:@"set"] || ![key hasSuffix:@":"]) {
return nil;
} // 移除set和:
NSRange range = NSMakeRange(, key.length - );
NSString *getter = [key substringWithRange:range]; // 小写
NSString *firstString = [[getter substringToIndex:] lowercaseString];
getter = [getter stringByReplacingCharactersInRange:NSMakeRange(, )
withString:firstString]; return getter;
}
@end
外界使用
创建Person类
Person.h
#import <Foundation/Foundation.h> @interface Person : NSObject @property (nonatomic, copy) NSString * name; @end
Person.m
#import "Person.h" @implementation Person @end
#import "ViewController.h"
#import "Person.h"
#import "NSObject+KVO.h" @interface ViewController () @property (nonatomic, strong) Person * p; @end @implementation ViewController - (void)viewDidLoad {
[super viewDidLoad]; Person * p = [[Person alloc] init];
[p ey_addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
self.p = p;
} // 监听的回调
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context
{
NSLog(@"%@", self.p.name);
} - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[super touchesBegan:touches withEvent:event]; self.p.name = [NSString stringWithFormat:@"%u", arc4random_uniform()];
} @end
更多内容--> 博客导航 每周一篇哟!!!
有任何关于iOS开发的问题!欢迎下方留言!!!或者邮件lieryangios@126.com 虽然我不一定能够解答出来,但是我会请教iOS开发高手!!!解答您的问题!!!
KVO底层实现原理,仿写KVO的更多相关文章
- Object-C知识点 (三) 单例 蒙版 刷新 KVO底层
#pragma mark - 单例方法(完整的方法) 系统的单例方法名称 sharedApplication defaultManager standardUserDefaults currentDe ...
- KVC和KVO的理解(底层实现原理)
1.KVC,即是指 NSKeyValueCoding,一个非正式的Protocol,提供一种机制来间接访问对象的属性.而不是通过调用Setter.Getter方法访问.KVO 就是基于 KVC 实现的 ...
- IOS-详解KVO底层实现
一.KVO (Key-Value Observing) KVO 是 Objective-C 对观察者模式(Observer Pattern)的实现.也是 Cocoa Binding 的基础.当被观察对 ...
- KVO内部实现原理
KVO的原理: 只要给一个对象注册一个监听, 那么在运行时, 系统就会自动给该对象生成一个子类对象, (格式如:NSKVONotifying_className), 并且重写自动生成的子类对象的被监听 ...
- PHP底层工作原理
最近搭建服务器,突然感觉lamp之间到底是怎么工作的,或者是怎么联系起来?平时只是写程序,重来没有思考过他们之间的工作原理: PHP底层工作原理 图1 php结构 从图上可以看出,php从下到上是一个 ...
- Java并发之底层实现原理学习笔记
本篇博文将介绍java并发底层的实现原理,我们知道java实现的并发操作最后肯定是由我们的CPU完成的,中间经历了将java源码编译成.class文件,然后进行加载,然后虚拟机执行引擎进行执行,解释为 ...
- Spring(二)IOC底层实现原理
IOC原理 将对象创建交给Spring去管理. 实现IOC的两种方式 IOC配置文件的方式 IOC注解的方式 IOC底层实现原理 底层实现使用的技术 1.1 xml配置文件 1.2 dom4j解析xm ...
- iOS分类底层实现原理小记
摘要:iOS分类底层是怎么实现的?本文将分如下四个模块进行探究分类的结构体编译时的分类分类的加载总结本文使用的runtime源码版本是objc4-680文中类与分类代码如下//类@interfaceP ...
- java并发编程系列七:volatile和sinchronized底层实现原理
一.线程安全 1. 怎样让多线程下的类安全起来 无状态.加锁.让类不可变.栈封闭.安全的发布对象 2. 死锁 2.1 死锁概念及解决死锁的原则 一定发生在多个线程争夺多个资源里的情况下,发生的原因是 ...
随机推荐
- 通过rpm 安装MYSQL
1.MYSQL Server端安装: 2.MYSQL client 安装 3.设置MYSQL密码(安装了MySql客户端才可以执行) ' 4.登录MYSQL mysql 的最简单的安装方法啦
- 【JAVAWEB学习笔记】05_jQuery基础
晨读单词: toggle:切换 each:每个(遍历) append:追加(内部追加,将B追加到A的内部结尾处) appendTo:追加(内部追加,将A追加到B的内部结尾处) prepend:追加(内 ...
- wiringPi安装
wiringPi安装 更新软件,输入以下指令: sudo apt-get update sudo apt-get upgrade 通过GIT获得wiringPi的源代码 git c ...
- 关于RFID2.4G 标签卡最新方案
它是一款针对RFID有源卡行业设计的,是一款单向的2.4G频段RF射频芯片,目前主要针对低功耗的校讯通, 2.4G停车场,电动车防盗, 闪光灯设备(引闪器) ,智能家居等领域.SI24R2E 同样与S ...
- 极光开发者沙龙 之 移动应用性能优化实践 【一】旧酒新瓶——换个角度提升 App 性能与质量
旧酒新瓶--换个角度提升 App 性能与质量 主讲人:高亮亮 --- 饿了么移动技术部高级iOS工程师,负责饿了么商家版iOS APP开发,对架构和系统底层有深入研究,擅长移动性能分析,troub ...
- 对 Servlet 的改进--------Struts2 引入
通过上一篇博客:Servlet 的详解 http://www.cnblogs.com/ysocean/p/6912191.html,我们大致知道了 Servlet 的基本用法.但是稍微分析一下 Ser ...
- javascript基础-对象
原理 万物皆为对象.假设将'莫德'(我)看成对象.莫德的属性有名字,性别,年龄等. 莫德的行为有吃饭,走路,睡觉等.莫德与他人的往来即对象间的交互.对象对应世界的一个实体.类,即管理对象的分类.如果莫 ...
- nodeJS之进程process对象
前面的话 process对象是一个全局对象,在任何地方都能访问到它,通过这个对象提供的属性和方法,使我们可以对当前运行的程序的进程进行访问和控制.本文将详细介绍process对象 概述 process ...
- 数据库MySQL纯净卸载
有些人在安装MySQL后,卸载后再次安装时,一直安装不上去,到最后不得不重装系统来安装MySQL.这里教大家如何将MySQL卸载干净,不影响下次安装. 卸载过程 1.停止mysql服务 2.进行卸载 ...
- Java对【JSON数据的解析】--fastjson解析法
要求:解析下面JSON数据 String string = "{no:1,name:'Android',employees:[{name:'zhangsan',age:20},{name:' ...