iOS 底层探索篇 —— KVC 底层原理
iOS 底层探索篇 —— KVC 底层原理
- 1. Method Swizzling的坑与应用
- 1.1 method-swizzling 是什么?
- 1.2 坑点
- 坑点1:method-swizzling使用过程中的确保执行一次
- 解决方案
- 坑点2:子类没有实现,父类实现了
- 解决方案
- 坑点3:父类子类都没有实现
- 解决方案
- class_replaceMethod, addMethod, method_exchangeImplementations 源码
- 2.KVC
- 2.1 KVC 设值
- setValueForKey流程图
- 2.2 KVC 取值
- valueForKey流程图
- 3.自定义KVC
- 3.1自定义设值
- 3.2 自定义取值
1. Method Swizzling的坑与应用
1.1 method-swizzling 是什么?
Method-Swizzling实际就是更换方法所对应的实现函数,其主要作用是在运行时将一个方法的实现替换成另一个方法的实现,这就是我们常说的iOS黑魔法
。method-swizzling最常用的应用是防止数组、字典等越界崩溃
。
下面的代码是否会无限递归呢?答案是不会的,因为交换方法后,lg_studentInstanceMethod
实际上是通过[self personInstanceMethod]
调用的,而 [self lg_studentInstanceMethod]
则是调用 personInstanceMethod
,所以不会无限递归。
1.2 坑点
坑点1:method-swizzling使用过程中的确保执行一次
所谓的一次性就是:mehod-swizzling
写在load方法中,而load方法会主动调用多次
,这样会导致方法的重复交换
,无法确保是否交换成功。
如何解决这个问题呢?
解决方案
可以通过单例设计原则,在OC中可以通过dispatch_once
实现单例,这样就可以使方法交换只执行一次
。
坑点2:子类没有实现,父类实现了
如果交换的方法在子类没有实现,在父类里面实现了,那么交换的时候会不会报错呢?
运行后发现报错了。这是因为,personInstanceMethod
已经被交换成了lg_studentInstanceMethod
。而在lg_studentInstanceMethod
里面调用了[self lg_studentInstanceMethod]
,而LGPerson
里面是没有lg_studentInstanceMethod
方法的,所以抱错。
为什么要有[self lg_studentInstanceMethod]呢?这是因为如果在满足一定条件下,那么就还会走原有的逻辑。比如数组越界,如果index < array.count, 那么就还走原有的逻辑。
解决方案
先通过class_addMethod
尝试给自己添加要交换的方法,如果添加成功,即类中没有这个方法,则通过class_replaceMethod
进行替换,如果添加失败则代表有这个方法,正常进行method_exchangeImplementations
。
坑点3:父类子类都没有实现
如果父类子类都没有实现,那么会有什么问题呢?运行后发现死循环了。那么就说明没有调换成功。所以sel lg_studentInstanceMethod
还是指向自己的imp
。当调用personInstanceMethod
之后,会进入lg_studentInstanceMethod
, 之后[self lg_studentInstanceMethod]
就会无限循环调用
自己,就导致了死循环。
解决方案
判断oriMethod
是否为空
,如果为空则添加swiMethod
方法并且添加imp实现
。
class_replaceMethod, addMethod, method_exchangeImplementations 源码
2.KVC
KVC的全称是Key-Value Coding
,翻译成中文是 键值编码
,键值编码是一种由NSKeyValueCoding
非正式协议启用的机制,对象采用该协议来提供对其属性的间接访问
。既可以通过一个字符串key来访问某个属性。这种间接访问机制补充了实例变量及其相关的访问器方法所提供的直接访问。
2.1 KVC 设值
一般来说,对对象属性赋值有2种方式。
- 直接通过setter方法赋值
- 通过KVC键值编码的相关API赋值
查看setValueForKey
方法,发现其在Foundation
里面。而Foundation
框架是不开源
的,那么接下来就去苹果官方文档
查找。
Key-Value Coding Programming Guide
在这里看到setValueForKey
方法的流程如下:
第一步: 按顺序查找名为
set<Key>:
,_set<Key>
或者setIs<Key>
的setter 方法。如果找到就调用它。
- 只要实现任意一个,那么就会将
调用这个方法
将属性的值设为传进来的值。如果存在多个
,则调用顺序在前面的
。- 如果没有则进入第二步
第二步:如果三个setter方法都没有实现,那么就检查
accessInstanceVariablesDirectly
是否返回YES
,是的话就则按顺序查找名为_<Key>
、_is<Key>
、<Key>
或is<Key>
的实例变量
。否则就跳到第三步。
- 只要找到任意一个,那么就会对
实例变量
进行赋值
,如果存在多个
,则赋值顺序在前面的
。- 如果没有就进入第三步
第三步: 如果找不到
setter方法
或实例变量
,系统会执行该对象的setValue:forUndefinedKey:方法
,默认抛出NSUndefinedKeyException
类型的异常
。
setValueForKey流程图
2.2 KVC 取值
kvc 取值方法是valueForKey:,方法流程如下:
第一步:首先按照
get<Key>
,<key>
,is<Key>
,_<key>
的方法顺序查找getter方法,
- 如果找到,则进入【第五步】
- 如果没有找到,则进入【第二步】
第二步:如果没有实现getter 方法,KVC会查找
countOf <Key>
,objectIn<Key> AtIndex :
和<key> AtIndexes :
- 如果找到
countOf <Key>
和其他两个中的一个方法,创建一个响应所有NSArray方法的集合代理对象即NSKeyValueArray,并返回该对象。否则就会去第三步。- 代理对象随后将接收到的所有NSArray消息转换为
countOf<Key>
,objectIn<Key> AtIndex:
和<key>AtIndexes:
消息的某种组合,发送消息给创建它的键值编码对象。如果原始对象也实现了一个名为get<Key>:range:
的可选方法,代理对象也会在适当的时候使用该方法。实际上,代理对象与键值编码对象一起工作来使属性可以像NSArray一样使用,即使它不是NSArray。
第三步:如果没有实现普通的getter 方法 或者 array 的组合getter 方法,那么就会同时查找
countOf <Key>
,enumeratorOf<Key>
和memberOf<Key>
这三个方法
- 如果这三个方法都找到了,创建一个集合代理对象来响应所有的NSSet方法并返回它。否则,继续步骤4
- 这个代理对象随后将它收到的任何NSSet消息转换成
countOf<Key>
、enumeratorOf<Key>
、memberOf<Key>
的一些组合: 发送消息到创建它的对象。实际上,代理对象与键值编码对象一起工作来使属性可以像NSSet一样使用,即使它不是NSSet。
第四步:如果没有实现普通的getter 方法 或者 集合 的组合getter 方法,并且accessInstanceVariablesDirectly返回YES,那么就会按顺序寻找实例变量
_<key>
,_is<Key>
,<Key>
, 或is<Key>
- 只要找到任意一个,那么就会取得这个实例变量的值并且去到第五步。如果存在
多个
,则获得顺序在前面的实例变量的值
。然后去第五步。- 否则就去第六步。
第五步:返回结果
- 如果搜索到的属性值是对象指针,只需返回结果。
- 如果该值是NSNumber支持的标量类型,将其存储在NSNumber实例中并返回该对象。
- 如果结果是NSNumber不支持的标量类型,将其转换为NSValue对象并返回该对象。
第六步:如果上面5步的方法均失败,系统会执行该对象的valueForUndefinedKey:方法,默认抛出NSUndefinedKeyException类型的异常
valueForKey流程图
3.自定义KVC
看过了设值和取值流程,我们可以根据苹果官方文档提供的查找规则进行实现。
3.1自定义设值
- 判断key是否为空
- 查找是否有 setter方法
set<Key>:
,_set<Key>
,setIs<Key>
,如果有则实现并返回。
- 判断accessInstanceVariablesDirectly的返回值是否为YES,可以则往下走,否则抛出异常。
- 查找自己的ivar列表中是否包含实例变量
_<key>
,_is<Key>
,<key>
,is<Key>
,找到就赋值。
- 如果都搜索不到,就抛出异常。
3.2 自定义取值
- 判断key非空
2. 按顺序查找方法:get<Key>
、 <key>
、countOf<Key>
、 objectIn<Key>AtIndex
判断accessInstanceVariablesDirectly的返回值是否为YES,可以则往下走,否则抛出异常。
查找自己的ivar列表中是否包含实例变量
_<key>
,_is<Key>
,<key>
,is<Key>
,找到就取值。
抛出异常。
iOS 底层探索篇 —— KVC 底层原理相关推荐
- iOS底层探索(二) - 写给小白看的Clang编译过程原理
iOS底层探索(一) - 从零开始认识Clang与LLVM 写在前面 编译器是属于底层知识,在日常开发中少有涉及,但在我的印象中,越接近底层是越需要编程基本功,也是越复杂的.但要想提升技术却始终绕不开 ...
- iOS 底层探索 - 消息转发
一.动态方法解析流程分析 我们在上一章<消息查找>分析到了动态方法解析,为了更好的掌握具体的流程,我们接下来直接进行源码追踪. 我们先来到 _class_resolveMethod 方法, ...
- iOS底层探索二(OC 中 alloc 方法 初探)
前言 相关文章: iOS底层探索一(底层探索方法) iOS底层探索三(内存对齐与calloc分析) iOS底层探索四(isa初探-联合体,位域,内存优化) iOS底层探索五(isa与类的关系) iOS ...
- IOS 多线程04-GCD详解 底层并发 API
IOS 多线程04-GCD详解 底层并发 API 注:本人是翻译过来,并且加上本人的一点见解. 前言 想要揭示出表面之下深层次的一些可利用的方面.这些底层的 API 提供了大量的灵活性,随之而来的是大 ...
- 【决战西二旗】|Redis面试热点之底层实现篇
来自:后端技术指南针 0.前言 最近一周没有技术文章产出,主要是Q4马上结束各种业务都在冲量,笔者一直都在疯狂工作甚至还有些焦虑到偶尔失眠,由于没有成块的时间研究新东西,所以就把之前看过的东西抽时间总 ...
- # iOS 一窥并发编程底层(一)
语歌博客 逻辑控制流 在我们系统中通常是会有其它程序在运行,进程是可以告诉每一个程序它是独自在使用处理器.这个时候如果有调试器单步去执行程序,就会出现一系列的程序计数器( PC ) 值,这些值唯一的对 ...
- mysql引擎层存储层_MySQL存储底层技术:InnoDB底层原理解读
原标题:MySQL存储底层技术:InnoDB底层原理解读 存储引擎 很多文章都是直接开始介绍有哪些存储引擎,并没有去介绍存储引擎本身.那么究竟什么是存储引擎?不知道大家有没有想过,MySQL是如何存储 ...
- OC底层探索(七) cache_t分析
cache_t 源码 在OC底层探索(五) 类的结构分析文章中,我们分析objc_class源码时,有提到过其属性 cache_t cache属性的大小为16字节. 那么我们今天就着重分析以下cach ...
- 驱动篇:底层驱动移植(四)(摘录)
驱动篇:底层驱动移植(四)(摘录) 时钟驱动 在一个 SoC 中,晶振. PLL .驱动和门等会形成一个时钟树形结构,在 Linux 2.6 中,也存有clk_get_rate ().clk_set_ ...
最新文章
- php嵌入html还是html嵌入php,php嵌入html有哪几种方法
- LeetCode: 929. Unique Email Addresses
- module 'torch.jit' has no attribute 'unused'
- bugku ctf 域名解析
- LeetCode 1293. 网格中的最短路径(DP/BFS)
- java url特殊字符转义字符_URL中包含有特殊字符,进行转义
- Java 静态变量和静态方法
- Visual Studio下的 JS CSS 压缩和编辑插件
- Python使用numpy计算矩阵特征值、特征向量与逆矩阵
- ffmpeg 最简单的转码封装mp4文件
- MVC模式在Java Web应用程序中的实现
- IF:4+ 铁代谢和免疫相关基因标记预测三阴性乳腺癌的临床结局和分子特征
- cdd matlab 算法,求翻译 CDD修复算法
- python 余弦值_余弦相似度
- Java入门(六)MySql 数据库
- SpringBoot实现文件上传和下载
- php匹配字符串中的url并替换为超链接
- ERES BRES的区别
- 测绘类专业计算机要学什么科目,测绘类专业选考科目要求是什么
- 我的计算机桌面被分成三格,用四宫格管理你的电脑桌面,工作效率蹭蹭蹭的提高...