美文网首页objc底层专题
Category 的实现原理,为什么只能加方法不能加属性

Category 的实现原理,为什么只能加方法不能加属性

作者: hanqingYang | 来源:发表于2020-08-24 12:04 被阅读0次

简述 Category 的实现原理

我们知道 Objective-C 通过 Runtime 运行时来实现动态语言这个特性,所有的类和对象,在 Runtime 中都是用结构体来表示的,Category 在 Runtime 中是用结构体 category_t 来表示的,下面是结构体 category_t 具体表示:

通过结构体 category_t 可以知道,在 Category 中我们可以增加实例方法、类方法、协议、属性。我们这里简述下 Category 的实现原理:

在编译时期,会将分类中实现的方法生成一个结构体 method_list_t 、将声明的属性生成一个结构体 property_list_t ,然后通过这些结构体生成一个结构体 category_t 。

然后将结构体 category_t 保存下来

在运行时期,Runtime 会拿到编译时期我们保存下来的结构体 category_t

然后将结构体 category_t 中的实例方法列表、协议列表、属性列表添加到主类中

将结构体 category_t 中的类方法列表、协议列表添加到主类的 metaClass 中

这里需要注意的是:category_t 中的方法列表是插入到主类的方法列表前面(类似利用链表中的 next 指针来进行插入),所以这里 Category 中实现的方法并不会真正的覆盖掉主类中的方法,只是将 Category 的方法插到方法列表的前面去了。运行时在查找方法的时候是顺着方法列表的顺序查找的,它只要一找到对应名字的方法,就会停止查找,这里就会出现覆盖方法的这种假象了。

// 这里大概就类似这样子插入newproperties->next = cls->data()->properties;cls->data()->properties = newproperties;,

通过上面的简述,我们大概了解了 Category 的实现原理,就可以知道 Extension 跟 Category 是两种实现模式,一个是在编译时期实现的,一个是在运行时期决定的。

Category 为什么不能添加实例变量

通过结构体 category_t ,我们就可以知道,在 Category 中我们可以增加实例方法、类方法、协议、属性。这里没有 objc_ivar_list 结构体,代表我们不可以在分类中添加实例变量。

因为在运行期,对象的内存布局已经确定,如果添加实例变量就会破坏类的内部布局,这个就是 Category 中不能添加实例变量的根本原因。

项目中用 Category 一般用来实现什么功能

通过分类来为已知的类扩展方法和属性,Category 不会为我们的属性添加实例变量和存取方法,我们可以通过关联对象这个技术来实现对象绑定

通过实现分类的 load 方法来实现 Method Swizzling

将一个类拆分成多个实现文件,典型的就是将项目中 AppDelegate 拆分。 AppDelegate 作为程序的入口,一般都会实现各种第三方 SDK 的初始化、写各种版本的容错代码、实现通知、支付逻辑等等功能,所以 AppDelegate 这个类很容易臃肿,这个时候可以通过实现 AppDelegate 分类来将不同的业务代码分离。

被 Category “覆盖” 的方法是有办法调用到的(但是项目中暂时还没遇到这种场景 - -/...)

关联对象存储原理

有人可能会有疑问,关联对象底层是怎么实现的呢,它是不是通过属性生成了成员变量,然后合并到了类对象的成员属性列表中去呢?其实不是的,关联对象是另外单独存储的,底层实现关联对象技术的核心对象有4个:

ObjcAssociation:这个对象里面有2个成员uintptr_t _policy和id _value,这两个很显然就是我们设置关联对象传入的参数policy和value。

ObjectAssociationMap:这是一个HashMap(以键值对方式存储,可以理解为是一个字典),以设置关联对象时传入的key值作为HashMap的键,以ObjcAssociation对象作为HashMap的值。比如一个分类添加了3个属性,那一个实例对象给这3个属性都赋值了,那么这个HashMap中就有3个元素,如果给这个实例对象的其中一个属性赋值为nil,那这个HashMap就会把这个属性对应的键值对给移除,然后HashMap中就还剩2个元素。

AssociationsHashMap:这也是一个HashMap,以设置关联属性时传入的参数object作为键(实际是对object对象通过某个算法计算出一个值作为键)。以ObjectAssociationMap作为值。所以当某个类(前提是这个类的分类中有设置关联对象)每实例化一个对象,这个HashMap就会新增一个元素,当某个实例化对象被释放时,其对应的键值对也会被这个HashMap给移除。注意整个程序运行期间,AssociationsHashMap只会有一个,也就是说所有的类的关联对象信息都是存储在这个HashMap中。

AssociationsManager:从名字就可以看出它是一个管理者,注意整个程序运行期间它也只有一个,他就只包含一个AssociationsHashMap。

总结

我们这是简单描述 Category 的实现原理以及项目中 Category 的一些运用,Category 的知识点远不止这些,对于 Category 具体实现代码可以阅读参考文献中的链接,看完可以对 Category 有更高层次的理解。

对于这道面试题,我们还可以扩展其它问题:关联对象是如何实现的,Category 中可以实现 load 方法吗等问题。这些问题我们在后面都会涉及。

相关文章

网友评论

    本文标题:Category 的实现原理,为什么只能加方法不能加属性

    本文链接:https://www.haomeiwen.com/subject/vixykttx.html