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 死锁概念及解决死锁的原则 一定发生在多个线程争夺多个资源里的情况下,发生的原因是 ...
随机推荐
- caffe的Matlab接口安装
参考博文:http://blog.csdn.net/thystar/article/details/50720691 0. Caffe安装及Matlab安装 1. Caffe中matcaffe配置 c ...
- prop()、attr()和data()
设置元素属性,用attr()还是prop()? 对于取值为true /false的属性,如 checked/selected/readonly或者disabled,使用prop(),其他属性使用 at ...
- Python之向日志输出中添加上下文信息
除了传递给日志记录函数的参数(如msg)外,有时候我们还想在日志输出中包含一些额外的上下文信息.比如,在一个网络应用中,可能希望在日志中记录客户端的特定信息,如:远程客户端的IP地址和用户名.这里我们 ...
- Zepto源码分析-event模块
源码注释 // Zepto.js // (c) 2010-2015 Thomas Fuchs // Zepto.js may be freely distributed under the MIT l ...
- 一天搞定CSS:盒模型content、padding、border、margin--06
1.盒模型 网页设计中常听的属性名:内容(content).填充(padding).边框(border).边界(margin), CSS盒子模式都具备这些属性. 这些属性我们可以用日常生活中的常见事物 ...
- 线程-join();
一.join()方法,官方描述 waits for this thread to die 等待当前线程死亡: 源码: //无参,默认调用join(0) public final void join ...
- ap.net core 教程(三) - 新建项目
ASP.NET Core - 新建项目 在这一章,我们将讨论如何在Visual Studio中创建一个新项目. 只要你安装了Visual Studio 2015的.net core工具,您就可以开始构 ...
- TeamViewer——可以实现在手机上随时远程控制你的电脑
小编今天给大家推荐一款强大的远程控制软件——TeamViewer,可以让你的手机连接你自己的电脑,不管你身处何处,只要电脑和手机都能联网,那么你就可以在手机上控制你的电脑了.以下介绍下如何安装和使用方 ...
- mysql主键约束和唯一性约束
主键约束和唯一性约束都是索引,它们的区别是: 主键字段可以确保唯一性,但主键字段不能为NULL. 唯一性约束可以确保唯一性,但唯一性约束的字段可以为NULL 唯一性约束对含有NULL的记录不起作用,即 ...
- 【CC2530入门教程-01】IAR集成开发环境的建立与项目开发流程
[引言] 本系列教程就有关CC2530单片机应用入门基础的实训案例进行分析,主要包括以下6部分的内容:1.CC2530单片机开发入门.2.通用I/O端口的输入和输出.3.外部中断初步应用.4.定时/计 ...