回顾

在前两篇博客中,已经介绍了KVO的相关操作,还有KVO的底层逻辑是通过动态生成子类,重写父类的方法实现的,那么我们如何自定义一个KVO呢?

iOS底层探索之KVO(一)—KVO简介
iOS底层探索之KVO(二)—KVO原理分析

1. 前期分析

系统的KVO是在NSObject的上面拓展了一些能力,如下图所示:

系统的KVO使用的三部曲是:

  • 添加监听addObserver
  • 监听回调observeValueForKeyPath
  • 移除监听removeObserver

我们也仿照系统的API自定义一个KVO,如下:

@interface NSObject (JP_KVO)- (void)jp_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
- (void)jp_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(nullable void *)context;
- (void)jp_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;@end

我们自定义KVO是对属性进行观察,那么成员变量是没有setter方法的,所以第一步就得过滤掉成员变量。如何动态生成子类,改变isa的指向,保存观察者,步骤大致如下:

  1. 验证是否存在setter方法 : 不让成员变量(实例变量)进来
  2. 动态生成子类
  3. 改变子类的isa的指向
  4. 保存我们的观察者
- (void)jp_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context{// 1: 验证是否存在setter方法 : 不让实例进来[self judgeSetterMethodFromKeyPath:keyPath];// 2: 动态生成子类Class newClass = [self createChildClassWithKeyPath:keyPath];// 3: isa的指向object_setClass(self, newClass);// 4: 保存观察者objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(KJPKVOAssiociateKey), observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

2. 验证是否存在setter方法

  • judgeSetterMethodFromKeyPath:keyPath
- (void)judgeSetterMethodFromKeyPath:(NSString *)keyPath{Class superClass    = object_getClass(self);SEL setterSeletor   = NSSelectorFromString(setterForGetter(keyPath));Method setterMethod = class_getInstanceMethod(superClass, setterSeletor);if (!setterMethod) {@throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"不好意思没有当前%@的setter",keyPath] userInfo:nil];}
}

判断是否有setter方法,没有就抛出异常信息

3. 动态生成子类

- (Class)createChildClassWithKeyPath:(NSString *)keyPath{NSString *oldClassName = NSStringFromClass([self class]);NSString *newClassName = [NSString stringWithFormat:@"%@%@",kjpKVOPrefix,oldClassName];Class newClass = NSClassFromString(newClassName);// 防止重复创建生成新类if (newClass) return newClass;/*** 如果内存不存在,创建生成* 参数一: 父类* 参数二: 新类的名字* 参数三: 新类的开辟的额外空间*/// 2.1 : 申请类newClass = objc_allocateClassPair([self class], newClassName.UTF8String, 0);// 2.2 : 注册类objc_registerClassPair(newClass);// 2.3.1 : 添加class : class的指向是JPPersonSEL classSEL = NSSelectorFromString(@"class");Method classMethod = class_getInstanceMethod([self class], classSEL);const char *classTypes = method_getTypeEncoding(classMethod);class_addMethod(newClass, classSEL, (IMP)jp_class, classTypes);// 2.3.2 : 添加setterSEL setterSEL = NSSelectorFromString(setterForGetter(keyPath));Method setterMethod = class_getInstanceMethod([self class], setterSEL);const char *setterTypes = method_getTypeEncoding(setterMethod);class_addMethod(newClass, setterSEL, (IMP)jp_setter, setterTypes);return newClass;
}
  • JP_class 返回父类信息
Class JP_class(id self,SEL _cmd){return class_getSuperclass(object_getClass(self));
}
  • 判断是否已经创建了子类,没有就创建
  • 申请类
  • 注册类
  • newClass不存在则调用objc_allocateClassPair创建kvo子类,并且重写- class方法
  • 添加setter
  • 返回newClass

4. 创建setter

创建setter方法主要是分两部分:调用父类方法以及发送通知

static void jp_setter(id self,SEL _cmd,id newValue){// 4: 消息转发 : 转发给父类// 改变父类的值 --- 可以强制类型转换void (*jp_msgSendSuper)(void *,SEL , id) = (void *)objc_msgSendSuper;// void /* struct objc_super *super, SEL op, ... */struct objc_super superStruct = {.receiver = self,.super_class = class_getSuperclass(object_getClass(self)),};//objc_msgSendSuper(&superStruct,_cmd,newValue)jp_msgSendSuper(&superStruct,_cmd,newValue);// 既然观察到了,下一步不就是回调 -- 让我们的观察者调用// - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context// 1: 拿到观察者id observer = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kJPKVOAssiociateKey));// 2: 消息发送给观察者SEL observerSEL = @selector(observeValueForKeyPath:ofObject:change:context:);NSString *keyPath = getterForSetter(NSStringFromSelector(_cmd));objc_msgSend(observer,observerSEL,keyPath,self,@{keyPath:newValue},NULL);
}
  • 调用父类方法可以通过objc_msgSendSuper实现,调用父类的setter(也可以通过performSelector调用

  • 通知观察者keypath可以通过_cmd转换获取,objectselfchange也可以获取到,context可以先不传。那么核心就是observer的获取。

  • 通过关联对象的方式,把observer进行存储

  • 拿到观察者了,就把消息发送给观察者,进行信息的回调处理

  • getterForSetter

#pragma mark - 从set方法获取getter方法的名称 set<Key>:===> key
static NSString *getterForSetter(NSString *setter){if (setter.length <= 0 || ![setter hasPrefix:@"set"] || ![setter hasSuffix:@":"]) { return nil;}NSRange range = NSMakeRange(3, setter.length-4);NSString *getter = [setter substringWithRange:range];NSString *firstString = [[getter substringToIndex:1] lowercaseString];return  [getter stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:firstString];
}

更多内容持续更新

iOS底层探索之KVO(三)—自定义KVO相关推荐

  1. iOS 底层探索篇 —— KVC 底层原理

    iOS 底层探索篇 -- KVC 底层原理 1. Method Swizzling的坑与应用 1.1 method-swizzling 是什么? 1.2 坑点 坑点1:method-swizzling ...

  2. iOS 底层探索 - 消息转发

    一.动态方法解析流程分析 我们在上一章<消息查找>分析到了动态方法解析,为了更好的掌握具体的流程,我们接下来直接进行源码追踪. 我们先来到 _class_resolveMethod 方法, ...

  3. iOS底层探索二(OC 中 alloc 方法 初探)

    前言 相关文章: iOS底层探索一(底层探索方法) iOS底层探索三(内存对齐与calloc分析) iOS底层探索四(isa初探-联合体,位域,内存优化) iOS底层探索五(isa与类的关系) iOS ...

  4. iOS底层探索(二) - 写给小白看的Clang编译过程原理

    iOS底层探索(一) - 从零开始认识Clang与LLVM 写在前面 编译器是属于底层知识,在日常开发中少有涉及,但在我的印象中,越接近底层是越需要编程基本功,也是越复杂的.但要想提升技术却始终绕不开 ...

  5. iOS底层-KVO分析与自定义

    KVO分析与自定义 背景 准备 KVO一些细节 KVO探索分析 KVO 底层原理 小结: KVO自定义 自定义KVO要知道: 1,KVO是对setter方法进行观察,过滤实例方法 2,添加KVO(核心 ...

  6. iOS底层原理之KVO本质

    面试中常会问道: iOS用什么方式实现对一个对象的KVO?(KVO的本质是什么?) 如何手动触发KVO 什么是KVO 首先需要了解KVO基本使用,KVO的全称 Key-Value Observing, ...

  7. iOS应用架构谈(三):网络层设计方案

    前言 网络层在一个App中也是一个不可缺少的部分,工程师们在网络层能够发挥的空间也比较大.另外,苹果对网络请求部分已经做了很好的封装,业界的AFNetworking也被广泛使用.其它的ASIHttpR ...

  8. app启动页数秒加载 代码_iOS 底层探索 - 应用加载

    一.前导知识 以下参考自 WWDC 2016 Optimizing App Startup Time : 1.1 Mach-O Mach-O is a bunch of file types for ...

  9. iOS底层原理之架构设计

    文章目录 何为架构? MVC - Apple版 MVC – 变种 MVP MVVM 设计模式 面试题 何为架构? 架构(Architecture):软件开发中的设计方案,类与类之间的关系.模块与模块之 ...

  10. iOS底层原理探究 第一探. 事件传递和响应者链

    一. 声明:  本文意在探讨, 也参考了几位大神的文章, 在最后我会把链接发出来, 如果有理解错误的地方, 请大神们指正哈! 二. 前言:  最近自己做项目的时候, 用到了UITabbarContro ...

最新文章

  1. 访问Webservice错误
  2. 工作374-前端margin:0 auto为什么会失效
  3. 没有bug队——加贝——Python 练习实例 29,30
  4. 解决MacOS升级后出现xcrun: error: invalid active developer path, missing xcrun的问题
  5. 如何通过a/a中的href刷新当前界面
  6. EXCEL电子档色环电阻计算器
  7. Codevs 1253 超级市场
  8. python贪吃蛇游戏代码详解外加中文_Python贪吃蛇游戏编写代码
  9. Verilog-半加器(简单组合逻辑)
  10. 【论文学习】Towards Accurate Oriented Object Detection in Aerial Images with Adaptive Multi-level Feature
  11. CANopen协议,上位机开发(C#)
  12. 【项目源码】JSP超市积分管理系统源码下载
  13. vue设置右边距_那些PPT高手,都是怎样设置软件的
  14. 繁体与简体之间的转化
  15. 大一C语言入门到底怎么学
  16. 自动设置IP地址的BAT
  17. sql server 子查询的两种方式
  18. iPhone王者找不到以前服务器,王者荣耀怎么找以前的区
  19. 九宫格 数独二进制解法
  20. Fast R-CNN 简单梳理

热门文章

  1. 201621123083 《Java程序设计》第9周学习总结
  2. 微信小程序------媒体组件(视频,音乐,图片)
  3. 003自动装配歧义性解决
  4. Vue学习笔记(六)
  5. Array 对象-sort()
  6. C程序推算你生日的日期
  7. Pandas:删除行、列---DataFram.drop()
  8. #pragma的一些用法
  9. Intel Quick Sync Video Encoder 2
  10. 8月7日 使用Jquery做表格的隔行变色,点击事件