Objective-C类别也叫分类,是一种不须要继承便可给类添加方法的语法技术。数组
一般咱们是用Category为一个类添加一些方法。咱们能够直接用相似对象对方法调用的样子直接对Category中的方法进行调用。好比下面的例子,为Person(Person类定义在.h和.m文件中了,图片没有给出)类定义了一个名为Test的Category。markdown
调用Category中的test方法与实例直接调用实例方法同样,也是经过消息发送机制(objc_sendMethod
)进行调用。app
咱们都知道相似这种方法调用是经过实例的isa指针查找类对象,在类对象中查找到相应的实例方法进行调用的,固然还包括superclass查找父类,这里就不作赘述了。由于经过isa指针查找方法已经在往深处看-ObjC对象中有过介绍。iphone
那么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
类型的结构体。oop
查看_category_t
结构体的结构:其中存放有:名称、类信息、实例方法和类方法列表、协议列表和属性列表。spa
实际上在runtime源码的最新版本objc4-723中对category_t的描述中添加了获取方法列表的方法和元类属性的方法,而且将properties分红了instanceProperties和_classProperties。3d
咱们能够看到在_OBJC_$_CATEGORY_Person_$_test
中,传入的值:指针
上面说过Category中的方法、属性、协议是在运行时合并到类中的,因此咱们在运行时的入口objc_os.mm文件中找到_objc_init
方法。咱们能够看到在方法上面有注释表示这是入口函数,会经过dyid去加载一些模块。code
顺着这个思路咱们查看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中的方法,由于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中查看和改变