ObjC中Category的原理简析
栏目: Objective-C · 发布时间: 5年前
内容简介:通常我们是用Category为一个类添加一些方法。我们可以直接用类似对象对方法调用的样子直接对Category中的方法进行调用。比如下面的例子,为Person(Person类定义在.h和.m文件中了,图片没有给出)类定义了一个名为Test的Category。调用Category中的test方法与实例直接调用实例方法一样,也是通过消息发送机制(我们都知道类似这种方法调用是通过实例的isa指针查找类对象,在类对象中查找到相应的实例方法进行调用的,当然还包括superclass查找父类,这里就不做赘述了。因为通过
通常我们是用Category为一个类添加一些方法。我们可以直接用类似对象对方法调用的样子直接对Category中的方法进行调用。比如下面的例子,为Person(Person类定义在.h和.m文件中了,图片没有给出)类定义了一个名为Test的Category。
调用Category中的test方法与实例直接调用实例方法一样,也是通过消息发送机制( objc_sendMethod
)进行调用。
我们都知道类似这种方法调用是通过实例的isa指针查找类对象,在类对象中查找到相应的实例方法进行调用的,当然还包括superclass查找父类,这里就不做赘述了。因为通过isa指针查找方法已经在往深处看-ObjC对象中有过介绍。
那么Category定义的方法存放在哪里呢?其实它们跟类的实例对象一样,也是存放在类对象中。同样,Category中定义的类方法,也是存放在元类对象中的。只不过将Category中的信息合并到类或元类对象中的操作是在运行时完成的而非编译的时。
Category的底层结构
使用 xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc mian.m -o main.cpp
将上述main.m转换成main.cpp文件,可以看到Person的Category (Test) 被转换成了下面这样一个 _category_t
类型的结构体。
查看 _category_t
结构体的结构:其中存放有:名称、类信息、实例方法和类方法列表、协议列表和属性列表。
实际上在runtime源码的最新版本objc4-723中对category_t的描述中添加了获取方法列表的方法和元类属性的方法,并且将properties分成了instanceProperties和_classProperties。
Category初始化传值
我们可以看到在 _OBJC_$_CATEGORY_Person_$_test
中,传入的值:
在runtime源码中窥探Category
上面说过Category中的方法、属性、协议是在运行时合并到类中的,所以我们在运行时的入口objc_os.mm文件中找到 _objc_init
方法。我们可以看到在方法上面有注释表示这是入口函数,会通过dyid去加载一些模块。
顺着这个思路我们查看 map_image
是通过 map_images_nolock(count, paths, mhdrs)
获得的,在这个方法中存在一个加载模块的方法。
加载模块的函数中有一个重排方法的函数,在这个函数中有一个 attachCategories(cls, cats, true /*flush caches*/);
函数。这个函数是实现附加分类的作用。参数分别传递了cls对象和Category列表。
在 attachCategories()
中将Category中的属性、方法和协议取出放到数组中,然后用cls取出类中的 class_rw_t
结构体,取出的methods、properties、protocols分别调用attachLists方法,并且将方法数组、属性数组、协议数组和数组的中元素的个数传递到attachLists中。
在attachLists函数中将之前存放方法、属性、协议的数组扩容至 oldCount+addedCount
大小。将原方法、属性、协议的数组从大数组的第一个位置移动到最后的位置,然后将 attachCategories
传入的列表拷贝到大数组的前面,完成Category中的方法、属性、协议向类对象或元类对象的合并。
将Category中的内容合并到类或元类在runtime中的操作顺序:
首先类或元类方法列表是一个二维数组:
runtime合并Category信息到类或元类对象中在runtime源码中的函数用轨迹:
attachLists图示:
使用Category中方法的一个问题
我们可以看到在没有将Category的方法列表加到类或元类对象中之前,数组中只有这一个列表,而合并之后,它们就被放到了类或元类对象的方法列表的最后面,所以我们调用在Category中重写的类中的实例方法或者类方法都会优先执行Category中的方法,因为isa在类对象或者元类对象中寻找方法的时候会首先在Category中找到,既然找到了就不会继续往下找了。
但是假如有多个扩展都重写了类的某个方法,首先必然是执行某个Category的方法,这个Category在方法列表的最前面,那么多个Category的方法列表在类或元类的方法列表中的顺序是怎么决定的呢?
我们看到 attachLists
中扩展中的方法、属性和协议列表是在 attachCategories
中生成的,生成的过程:
通过 i--
来进行一个while循环,其中i是 int i = cats->count;
即cats的个数。而mlist、proplists和protolists是进行 ++
操作的,操作的结果造成了先取出cats列表最后一个放到mlist,propcounts的最前面。cats是按照编译顺序排列的,所以后编译的cat中取出的方法、属性和协议列表,分别放在mlist、proplists和protolists的最前面。
所以isa指针也会按照上述顺序查找方法,即后编译的cats中的方法会先调用。
**注:**编译顺序可以在Xcode中查看和改变
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- fasthttp原理简析
- ObjC中KVO原理简析
- Android DEPPLINK、APPLink 原理简析
- 某数加密的流程与原理简析
- CocosCreator 引擎资源加载与释放原理简析
- 简析OC中对象占用内存的原理
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Prometheus: Up & Running
Brian Brazil / O'Reilly Media / 2018-7-9 / USD 49.99
Get up to speed with Prometheus, the metrics-based monitoring system used by tens of thousands of organizations in production. This practical guide provides application developers, sysadmins, and DevO......一起来看看 《Prometheus: Up & Running》 这本书的介绍吧!