1. Category的作用
- 可以减少单个文件的体积
- 可以把不同的功能组织到不同的Category中
- 可以按需加载
- 声明私有方法
- 把framework的私有方法公开
Tips:比如一个类的方法,没有在.h文件中暴露出来,我们可以给这个类创建一个分类,在分类的.h文件中,去声明这个私有方法。
Extension 扩展
- 类的一部分
- 编译时期就确定的
- 隐藏类的私有属性
Category
- 运行时期确定的
- 不能添加变量(编译时期,对象的内存布局已经确定了,添加变量会破坏类的内存布局)
2. Category 底层原理探索
Category 的数据结构
struct category_t {
const char *name; // 所属类的名称
classref_t cls; // 要扩展的类对象(编译时期是没有值的)
struct method_list_t *instanceMethods; // 实例方法
struct method_list_t *classMethods; // 类方法
struct protocol_list_t *protocols; // 协议
struct property_list_t *instanceProperties; // 实例属性
// Fields below this point are not always present on disk.
struct property_list_t *_classProperties; // 类属性
method_list_t *methodsForMeta(bool isMeta) {
if (isMeta) return classMethods;
else return instanceMethods;
}
property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
};
从上面的Category的数据结构,我们就可以看出category的可为(可以添加实例方法,类方法,甚至可以实现协议,添加属性)和不可为(无法添加实例变量)。
2.1 代码调用流程分析
-
2.1.1 编译时期
可以通过clang 来编译一下:
clang -rewrite-objc DDPerson+Test.m
DDPerson+Test.m就是我随便创建的一个分类的 .m文件,使用上述命令编译完成后,会在该文件同级目录下生成一个DDPerson+Test.cpp文件。
Tips:编译过后的东西,都会存在Mach-O的可执行文件当中。
我们可以打开该文件找到分类的结构体定义 _category_t:
再向下可以看到这个分类的结构体到底存放了什么内容:
static struct _category_t _OBJC_$_CATEGORY_DDPerson_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) =
// 这个分类它存放在Mach-O文件__objc_const的字段中
{
"DDPerson", // 当前类的名称
0, // &OBJC_CLASS_$_DDPerson, 指向类对象的指针,运行时依赖 当前类的名称进行绑定
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_DDPerson_$_Test, // 当前分类里面的实例方法
0,
0,
0,
};
我们可以看到第三个 &OBJC$CATEGORY_INSTANCE_METHODS_DDPerson... ,是编译器生成的当前分类的实例方法列表。
因为我们只是给分类添加了一个方法,所以其余的类方法列表,协议列表,属性列表,都是0,也就是空的。
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[1];
} _OBJC_$_CATEGORY_INSTANCE_METHODS_DDPerson_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
// 下面三个参数分别是 方法编号 方法签名 真实的函数地址
{{(struct objc_selector *)"dd_privateTest", "v16@0:8", (void *)_I_DDPerson_Test_dd_privateTest}}
};
这一步就是生成分类的方法列表,用这个方法列表,初始化分类本身。
到现在为止分类在编译时期做的事情差不多已经完成了,
总结来说就是把当前的分类编译成结构体,然后把对应的字段填充上相应的数据。
但是,编译最后的分类数据放在哪里呢?我们继续向下看:
static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [1] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= {
&_OBJC_$_CATEGORY_DDPerson_$_Test,
};
上面是非常关键的,所有的分类都会存在 __DATA 数据段下的的 __objc_catlist section当中。
到目前为止,在编译时期对于分类的操作都已经完成了,运行时期,App就会从 __objc_catlist section中去读取所有的分类了。
-
2.1.2 运行时期
运行时期就要开始加载编译时期存储的所有分类了。
那么Category是如何被加载的呢?
- dyld 是苹果的动态加载器,用来加载 image (不是指图片,而是指 Mach-O 格式的二进制文件)
- 当程序启动的时候,系统内核首先会加载dyld,而dyld会将我们APP所以来的各种库加载到内存空间中,其中就包括libobjc库(OC和runtime),这些工作,是在APP的main函数执行前完成的。
- _objc_init 是 OC runtime 的入口函数,在这里面主要功能是读取Mach-O 文件 OC对应的 Segment section,并根据其中的数据代码信息,完成为OC的内存布局,以及初始化runtime相关的数据结构。
我们来看下_objc_init方法
void _objc_init(void)
{
static bool initialized = false;
if (initialized) return;
initialized = true;
// fixme defer initialization until an objc-using image is found?
environ_init();
tls_init();
static_init();
lock_init();
exception_init();
// 我们主要关注下面这个方法,这个方法注册了三个回调函数:
// dyld将image加载入内存的时候(将image映射到内存空间的时候)会执行 map_images
// dyld初始化image模块完成的时候调用 load_images(load方法也会在这个时候调用)
// dyld将image移出内存的时候 unmap_image函数会被调用
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
}
将分类加载到当前的类的操作是在 map_images 回调函数中完成的,下面我们先去看下这个 回调函数:
我们忽略不必要的代码走到下面这个函数中:
void
map_images_nolock(unsigned mhCount, const char * const mhPaths[],
const struct mach_header * const mhdrs[])
在这个函数底部,我们可以看到 _read_images的函数:
if (hCount > 0) {
_read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
}
_read_images 最主要的作用是: 读取OC相关的 Section段(Mach-O文件中的数据段),并进行初始化。
我们继续看 _read_images这个函数,直接到分类相关的部分:
// EACH_HEADER 就是遍历头文件
// Discover categories.
for (EACH_HEADER) {
category_t **catlist =
_getObjc2CategoryList(hi, &count);
bool hasClassProperties = hi->info()->hasCategoryClassProperties();
for (i = 0; i < count; i++) {
category_t *cat = catlist[I];
Class cls = remapClass(cat->cls);
if (!cls) {
// Category's target class is missing (probably weak-linked).
// Disavow any knowledge of this category.
catlist[i] = nil;
if (PrintConnecting) {
_objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with "
"missing weak-linked target class",
cat->name, cat);
}
continue;
}
...
我们拿到的 catlist 就是上节讲到的编译器为我们生成的category_t数组,它是通过
_getObjc2ClassList 这个方法拿到的,我们来关注下 _getObjc2ClassList 这个方法,点进去可以看到:
TSECT(_getObjc2NonlazyClassList, classref_t, "__objc_nlclslist");
// 看这个
GETSECT(_getObjc2CategoryList, category_t *, "__objc_catlist");
GETSECT(_getObjc2NonlazyCategoryList, category_t *, "__objc_nlcatlist");
GETSECT(_getObjc2ProtocolList, protocol_t *, "__objc_protolist");
在编译时期,我们可以知道,所有的分类都是存放在__objc_catlist 这个section当中(或者说对应这个key值),所以这里就是读取这个 __objc_catlist 对应的数据。
读取完成之后,我们继续往下看,下面这段代码:
// 处理这个Category
// 首先,将Category注册到它的目标类。
// 然后,如果实现了类,则重构类的方法列表(等等)。
bool classExists = NO;
// 把category的实例方法、协议以及属性添加到类上
if (cat->instanceMethods || cat->protocols
|| cat->instanceProperties)
{// 把分类注册到它的目标类建立一个关联
addUnattachedCategoryForClass(cat, cls, hi);
if (cls->isRealized()) {
// 重构类的方法列表
remethodizeClass(cls);
classExists = YES;
}
if (PrintConnecting) {
_objc_inform("CLASS: found category -%s(%s) %s",
cls->nameForLogging(), cat->name,
classExists ? "on existing class" : "");
}
}
// 把category的类方法和协议添加到类的metaclass上
if (cat->classMethods || cat->protocols
|| (hasClassProperties && cat->_classProperties))
{
addUnattachedCategoryForClass(cat, cls->ISA(), hi);
if (cls->ISA()->isRealized()) {
remethodizeClass(cls->ISA());
}
if (PrintConnecting) {
_objc_inform("CLASS: found category +%s(%s)",
cls->nameForLogging(), cat->name);
}
}
上面这段代码,主要的功能如下:
a. 把category的实例方法、协议以及属性添加到类上
b. 把category的类方法和协议添加到类的metaclass上
在上述的代码片段里,addUnattachedCategoryForClass只是把类和category做一个关联映射,而remethodizeClass才是真正去处理添加事宜的功臣。
那好,我们再去分析 remethodizeClass 这个关键方法:
// 在runtime源码中,我们翻译一下这个函数的注释,就能知道这个函数的作用了
// 1.将当前分类附加到现有类。
// 2.重新排列cls的方法列表、协议列表和属性列表。
// 3.更新cls及其子类的方法缓存。
static void remethodizeClass(Class cls)
{
category_list *cats;
bool isMeta;
runtimeLock.assertLocked();
isMeta = cls->isMetaClass();
// Re-methodizing: check for more categories
if ((cats = unattachedCategoriesForClass(cls, false/*not realizing*/))) {
if (PrintConnecting) {
_objc_inform("CLASS: attaching categories to class '%s' %s",
cls->nameForLogging(), isMeta ? "(meta)" : "");
}
attachCategories(cls, cats, true /*flush caches*/);
free(cats);
}
}
我们看到上面的方法,主要是调用 attachCategories 这个方法,
那么我们继续,在 attachCategories 函数中看到 prepareMethodList函数:
...
prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
rw->methods.attachLists(mlists, mcount);
free(mlists);
if (flush_caches && mcount > 0) flushCaches(cls);
...
这个函数的关键内容如下,它只是把所有category的实例方法列表拼成了一个大的实例方法列表:
...
// 添加方法列表到数组
// 重新分配方法列表。
// The new methods are PREPENDED to the method list array.
for (int i = 0; i < addedCount; i++) {
method_list_t *mlist = addedLists[I];
assert(mlist);
// Fixup selectors if necessary
if (!mlist->isFixedUp()) {
//
fixupMethodList(mlist, methodsFromBundle, true/*sort*/);
}
// Scan for method implementations tracked by the class's flags
if (scanForCustomRR && methodListImplementsRR(mlist)) {
cls->setHasCustomRR();
scanForCustomRR = false;
}
if (scanForCustomAWZ && methodListImplementsAWZ(mlist)) {
cls->setHasCustomAWZ();
scanForCustomAWZ = false;
}
}
...
准备好了这个方法列表之后之后,我们继续看
...
prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
rw->methods.attachLists(mlists, mcount);
free(mlists);
if (flush_caches && mcount > 0) flushCaches(cls);
...
主要是 rw->methods.attachLists(mlists, mcount); 这个方法
void attachLists(List* const * addedLists, uint32_t addedCount) {
if (addedCount == 0) return;
if (hasArray()) {
// many lists -> many lists
// 旧的大小,指的是原先方法列表的大小
uint32_t oldCount = array()->count;
// 新的就是 旧的 + 分类中要添加的
uint32_t newCount = oldCount + addedCount;
// 然后就是把新的方法添加到旧的方法列表
setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
array()->count = newCount;
memmove(array()->lists + addedCount, array()->lists,
oldCount * sizeof(array()->lists[0]));
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
else if (!list && addedCount == 1) {
// 0 lists -> 1 list
list = addedLists[0];
}
else {
// 1 list -> many lists
List* oldList = list;
uint32_t oldCount = oldList ? 1 : 0;
uint32_t newCount = oldCount + addedCount;
setArray((array_t *)malloc(array_t::byteSize(newCount)));
array()->count = newCount;
if (oldList) array()->lists[addedCount] = oldList;
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
}
这里面主要是使用 memmove memcpy 两个方法
都是用来做拷贝的操作,用来移动内存空间,为将要添加的方法腾出内存空间:
void *memcpy(void *__dst, const void *__src, size_t __n);
void *memmove(void *__dst, const void *__src, size_t __len);
// 把当前 src 指向的位置,拷贝len的长度,放到dst的位置
比如:1 2 3 4 5 6 7
memmove(1的位置,4的位置,长度3)
完成之后就是 4 5 6 4 5 6 7
就是把 456 从 4 的位置,拷贝到 1 的位置
这种场景下,使用memcpy 也可以。
如果是另一个场景:将 1 的位置 长度为 3 拷贝到 3 的位置,
那么**有可能**会出现 12121567 这种情况
所以这种内存重叠的情况下,memcpy 函数 有可能成功,有可能失败。
然后我们分析下 代码中
memmove(array()->lists + addedCount, array()->lists,
oldCount * sizeof(array()->lists[0]));
他的作用:
如果原有方法有10个,新添加的方法有3个,一共有13个,这句代码的作用就是将原有的10个方法,放到13个方法的后10个
刚开始是
旧 旧 旧 旧 旧 旧 旧 旧 旧 旧 空 空 空
变成
空 空 空 旧 旧 旧 旧 旧 旧 旧 旧 旧 旧
这样,前面三个方法就空出来了,因为这个场景刚好是上面介绍 memmove memcpy函数时的第二个场景,有可能会发生内存重叠,所以这里使用的是memmove。
前面3个方法空出来了,此时我们就要把需要添加的三个方法放到这里了,这里不会发生内存重叠,所以我们使用memcpy
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
它就是拷贝要添加的方法 addedLists 地址,长度为要添加的长度 addedCount 放到lists中。
从attachLists函数中我们可以看到,分类的方法覆盖掉了主类的方法,分类方法会添加到主类方法的前面,这是因为运行时在查找方法的时候是顺着方法列表的顺序查找的,它只要一找到对应名字的方法,就会罢休,殊不知后面可能还有一样名字的方法:
分类方法 分类方法 主类方法 主类方法。
我们可以打印出类的方法,然后通过代码可以调用到主类的原有的方法。
我们现在可以总结一下map_images中对分类做的事情了:
- _getObjc2CategoryList方法调用__objc_catlist section 段记录的所有Category,存放到category_t*数组
- 依次读取category_t*数组
- cat和cls(主类)进行映射
- remethodizeClass (修改method_list的结构(memmove memcpy))
3.关联对象探索
我们在开发中经常使用关联对象,在分类中添加一个属性:
@interface DDPerson (Test)
@property (nonatomic, copy) NSString *name;
@end
我们知道在主类中的属性其实包含三个东西:getter setter ivar, 这种在分类中声明的属性是没有ivar的,只是生成了一个存取方法,以及存储当前value的容器(ObjcAssociation 下面会讲到),因为上面也提到过,分类是不能添加成员变量的。
3.1 设置关联对象
在给分类关联对象的时候,我们会调用这个方法:
objc_setAssociatedObject(关联到哪个对象, 存储的key, 要关联的对象, 关联策略)
下面,我们就以这个方法为切入点,从源码中看一下:
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) {
_object_set_associative_reference(object, (void *)key, value, policy);
}
实际上是调用了 _object_set_associative_reference 这个方法,继续看:
void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {
ObjcAssociation old_association(0, nil);
// 取了一个新的value,根据策略设置
id new_value = value ? acquireValue(value, policy) : nil;
{
// 关联对象的管理者
AssociationsManager manager;
AssociationsHashMap &associations(manager.associations());
// 当前要关联到的对象的地址按位取反(作为当前hashmap当中的key)
disguised_ptr_t disguised_object = DISGUISE(object);
if (new_value) {
// c++ 语法,获取一个迭代器,找到 disguised_object 作为key时对应的value,里面的 j->first 指的是key j->second 指的是key对应的value
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) { // 如果disguised_object 对应的value有值,说明该对象有过关联
// 拿到 disguised_object 作为key 对应的value,也是一个map
ObjectAssociationMap *refs = i->second;
// 再以key值获取一个新的迭代器,这个key是我们传进来的,是我们所定义的
ObjectAssociationMap::iterator j = refs->find(key);
if (j != refs->end()) { // 如果用户传进的key有对应的value
// 获取到旧的value,赋给old_association,最后会被释放掉
old_association = j->second;
// 赋值新的value,ObjcAssociation 来存储我们的关联属性
j->second = ObjcAssociation(policy, new_value);
} else { // 如果用户传进的key没有对应的value
// 直接赋值新的value
(*refs)[key] = ObjcAssociation(policy, new_value);
}
} else {
// 如果这个对象是第一次关联,会走到这里,先创建一个Map,以disguised_object 作为key存进manager管理的hashmap,再将new_value 和 policy生成的 ObjcAssociation 对象,以key存入 Map
ObjectAssociationMap *refs = new ObjectAssociationMap;
associations[disguised_object] = refs;
(*refs)[key] = ObjcAssociation(policy, new_value);
// 设置当前object的isa指针,添加一个标识,说明这个对象有关联对象,在这个对象销毁的时候,去销毁这些关联对象
object->setHasAssociatedObjects();
}
} else {
// 如果是nil,则断开关联
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
ObjectAssociationMap *refs = i->second;
ObjectAssociationMap::iterator j = refs->find(key);
if (j != refs->end()) {
old_association = j->second;
refs->erase(j);
}
}
}
}
// 释放 old_association
if (old_association.hasValue()) ReleaseValue()(old_association);
}
代码很长,我们可以从上到下一点一点的分析:
a.先进行内存管理
// 这个方法是判断value有值的话,就去调用acquireValue 生成一个新的对象,进行内存管理
id new_value = value ? acquireValue(value, policy) : nil;
// 看下源码
acquireValue(value, policy) =>
// 清晰明了,根据关联策略,对value进行操作, objc_retain ((id(*)(id, SEL))objc_msgSend)(value, SEL_copy); 这两个操作就很熟悉了,strong copy关键字的内部实现。
static id acquireValue(id value, uintptr_t policy) {
switch (policy & 0xFF) {
case OBJC_ASSOCIATION_SETTER_RETAIN:
return objc_retain(value);
case OBJC_ASSOCIATION_SETTER_COPY:
return ((id(*)(id, SEL))objc_msgSend)(value, SEL_copy);
}
return value;
}
b. AssociationsManager 初始化其管理的hashmap
// 管理关联对象,其内部有一个static的 hashmap,用来管理关联对象的
AssociationsManager manager;
// 这个是初始化hashMap, 下面代码中有这个方法的实现
AssociationsHashMap &associations(manager.associations());
=> 点进去看下 AssociationsManager:
// 从下面的注释中我们也可以看到 这个hashMap 以对象的指针作为key的,value仍然是一个hashMap ,我们先了解下 AssociationsManager 的作用就是维护了一个hashmap.
class AssociationsManager {
// associative references: object pointer -> PtrPtrHashMap.
static AssociationsHashMap *_map;
public:
AssociationsManager() { AssociationsManagerLock.lock(); }
~AssociationsManager() { AssociationsManagerLock.unlock(); }
AssociationsHashMap &associations() {
if (_map == NULL)
_map = new AssociationsHashMap();
return *_map;
}
};
存的过程,可以通过下面这张图来理解,就会非常的清楚:
3.2 取关联对象
理解了怎么存的也就明白如何取的了,贴上源码:
id _object_get_associative_reference(id object, void *key) {
id value = nil;
uintptr_t policy = OBJC_ASSOCIATION_ASSIGN;
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.associations());
disguised_ptr_t disguised_object = DISGUISE(object);
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
ObjectAssociationMap *refs = i->second;
ObjectAssociationMap::iterator j = refs->find(key);
if (j != refs->end()) {
ObjcAssociation &entry = j->second;
value = entry.value();
policy = entry.policy();
if (policy & OBJC_ASSOCIATION_GETTER_RETAIN) {
objc_retain(value);
}
}
}
}
if (value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) {
objc_autorelease(value);
}
return value;
}
3.3 关联对象如何释放呢?
是在关联到的对象释放的时候,去释放它的关联对象。
我们可以从dealloc 方法入手去探究一下,这个隐藏的很深:
- (void)dealloc
↓
_objc_rootDealloc(self);
obj->rootDealloc();
object_dispose((id)this);
objc_destructInstance(obj);
终于到了objc_destructInstance方法:
void *objc_destructInstance(id obj)
{
if (obj) {
// Read all of the flags at once for performance.
bool cxx = obj->hasCxxDtor();
bool assoc = obj->hasAssociatedObjects();
// This order is important.
if (cxx) object_cxxDestruct(obj);
if (assoc) _object_remove_assocations(obj);
obj->clearDeallocating();
}
return obj;
}
我们可以看到是判断 assoc 这个变量为真时,去调用 _object_remove_assocations 移除关联对象的方法,获取 assoc 值的方法是
obj->hasAssociatedObjects(); 这个方法中其实就是获取在设置关联对象时,给isa设置的那个标志。
我们继续看 _object_remove_assocations 这个方法:
void _object_remove_assocations(id object) {
vector< ObjcAssociation,ObjcAllocator > elements;
{
// 获取manager
AssociationsManager manager;
AssociationsHashMap &associations(manager.associations());
// 如果size为0,说明没有关联属性,直接return
if (associations.size() == 0) return;
// 对象的地址取反
disguised_ptr_t disguised_object = DISGUISE(object);
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
// 获取到disguised_object 对应的map
ObjectAssociationMap *refs = i->second;
// 获取到所有的关联对象,放入elements
for (ObjectAssociationMap::iterator j = refs->begin(), end = refs->end(); j != end; ++j) {
elements.push_back(j->second);
}
// remove the secondary table.
delete refs;
associations.erase(i);
}
}
// 释放掉
for_each(elements.begin(), elements.end(), ReleaseValue());
}
4. load方法探究
调用规则
- 一个类的load方法在所有父类load 方法调用之后调用
- 分类的load方法在当前类的load方法调用之后调用
- 分类load的调用顺序和编译顺序有关
在前面,我们有讲到objc_init是runtime的入口函数
void _objc_init(void)
{
static bool initialized = false;
if (initialized) return;
initialized = true;
// fixme defer initialization until an objc-using image is found?
environ_init();
tls_init();
static_init();
lock_init();
exception_init();
// 我们主要关注下面这个方法,这个方法注册了三个回调函数:
// dyld将image加载入内存的时候(将image映射到内存空间的时候)会执行 map_images
// dyld初始化image模块完成的时候调用 load_images(load方法也会在这个时候调用)
// dyld将image移出内存的时候 unmap_image函数会被调用
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
}
load方法也是在 load_images 中调用的,我们现在就去从load_images方法入手去看一看:
void
load_images(const char *path __unused, const struct mach_header *mh)
{
// Return without taking locks if there are no +load methods here.
if (!hasLoadMethods((const headerType *)mh)) return;
recursive_mutex_locker_t lock(loadMethodLock);
// Discover load methods
{
mutex_locker_t lock2(runtimeLock);
// 准备
prepare_load_methods((const headerType *)mh);
}
// Call +load methods (without runtimeLock - re-entrant) 调用
call_load_methods();
}
这个方法分为两块,prepare_load_methods()和 call_load_methods();
4.1 prepare_load_methods
我们这边先分析 prepare_load_methods (),他是先把各个类或分类load方法都准备好:
void prepare_load_methods(const headerType *mhdr)
{
size_t count, I;
runtimeLock.assertLocked();
// 从Mach-o 文件加载类的列表
classref_t *classlist =
_getObjc2NonlazyClassList(mhdr, &count);
for (i = 0; i < count; i++) {
// 数组[,,] 有顺序
// 调整类的顺序
schedule_class_load(remapClass(classlist[i]));
}
// 针对分类的操作
category_t **categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
for (i = 0; i < count; i++) {
category_t *cat = categorylist[I];
Class cls = remapClass(cat->cls);
if (!cls) continue; // category for ignored weak-linked class
realizeClass(cls);
assert(cls->ISA()->isRealized());
add_category_to_loadable_list(cat);
}
}
4.1.1 针对类的load方法的操作
我们去看下 schedule_class_load 这个方法:
static void schedule_class_load(Class cls)
{
if (!cls) return;
assert(cls->isRealized()); // _read_images should realize
if (cls->data()->flags & RW_LOADED) return;
// Ensure superclass-first ordering
schedule_class_load(cls->superclass);
// 根据cls,将cls的load方法添加到一个全局的数组中
add_class_to_loadable_list(cls);
cls->setInfo(RW_LOADED);
}
这个方法是递归调用,目的就是遵循上面的第一条规则,父类的load方法,在子类的load方法之前调用,也就是将类和类的load方法,这样存入全局的一个容器(可以理解为数组)中:
[<父类,父类load方法>,<父类,父类load方法>,<子类,子类load方法>]
上面说到添加到一个全局的容器中,这一步是如何操作的呢?就是add_class_to_loadable_list:
void add_class_to_loadable_list(Class cls)
{
IMP method;
loadMethodLock.assertLocked();
method = cls->getLoadMethod();
if (!method) return; // Don't bother if cls has no +load method
if (PrintLoading) {
_objc_inform("LOAD: class '%s' scheduled for +load",
cls->nameForLogging());
}
if (loadable_classes_used == loadable_classes_allocated) {
// 字节对齐(???)
loadable_classes_allocated = loadable_classes_allocated*2 + 16;
loadable_classes = (struct loadable_class *)
realloc(loadable_classes,
loadable_classes_allocated *
sizeof(struct loadable_class));
}
// loadable_classes 就是一个全局的容器
loadable_classes[loadable_classes_used].cls = cls;
loadable_classes[loadable_classes_used].method = method;
loadable_classes_used++;
}
到现在 schedule_class_load(remapClass(classlist[i]));
这个方法我们就分析完了,它就是调整顺序,先将所有父类的load方法存入全局容器中,再存入当前类的load方法。
4.1.2 针对分类load方法的操作
接下来,就到了对于分类的操作了,还回到prepare_load_methods 这个方法
void prepare_load_methods(const headerType *mhdr)
{
...
// 针对分类的操作
category_t **categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
for (i = 0; i < count; i++) {
category_t *cat = categorylist[I];
Class cls = remapClass(cat->cls);
if (!cls) continue; // category for ignored weak-linked class
realizeClass(cls);
assert(cls->ISA()->isRealized());
add_category_to_loadable_list(cat);
}
}
我们知道分类load的调用顺序是 和编译顺序有关,上面的代码中我们可以看到,当从Mach-o文件中读取到 categorylist 的时候,顺序就已经决定了,谁在前面就会先去调用谁的load方法,所以在下面就会直接调用 add_category_to_loadable_list(cat)方法:
void add_category_to_loadable_list(Category cat)
{
IMP method;
loadMethodLock.assertLocked();
method = _category_getLoadMethod(cat);
// Don't bother if cat has no +load method
if (!method) return;
if (PrintLoading) {
_objc_inform("LOAD: category '%s(%s)' scheduled for +load",
_category_getClassName(cat), _category_getName(cat));
}
if (loadable_categories_used == loadable_categories_allocated) {
loadable_categories_allocated = loadable_categories_allocated*2 + 16;
loadable_categories = (struct loadable_category *)
realloc(loadable_categories,
loadable_categories_allocated *
sizeof(struct loadable_category));
}
loadable_categories[loadable_categories_used].cat = cat;
loadable_categories[loadable_categories_used].method = method;
loadable_categories_used++;
}
这个方法跟上面对于类的load方法操作一样,初始化了loadable_categories 这么一个容器,存储分类的load方法:
[<分类,load方法>,<分类,load方法>,<分类,load方法>]
到此,prepare_load_methods 的方法也就执行完毕了,它就是准备好了两个全局容器loadable_classes和loadable_category,分别存储类的load方法,分类的load方法。
4.2 call_load_methods
准备好了之后,就要开始调用了:
void call_load_methods(void)
{
static bool loading = NO;
bool more_categories;
loadMethodLock.assertLocked();
// Re-entrant calls do nothing; the outermost call will finish the job.
if (loading) return;
loading = YES;
void *pool = objc_autoreleasePoolPush();
do {
// 1. Repeatedly call class +loads until there aren't any more
while (loadable_classes_used > 0) {
// 先调用类的load方法
call_class_loads();
}
// 2. Call category +loads ONCE 再调用分类的load方法
more_categories = call_category_loads();
// 3. Run more +loads if there are classes OR more untried categories
} while (loadable_classes_used > 0 || more_categories);
objc_autoreleasePoolPop(pool);
loading = NO;
}
在上面可以看到,是先 call_class_loads 调用类的load方法,再 call_category_loads 调用分类的load方法。
4.2.1 call_class_loads 调用类的load方法
static void call_class_loads(void)
{
int I;
// Detach current loadable list.
struct loadable_class *classes = loadable_classes;
int used = loadable_classes_used;
loadable_classes = nil;
loadable_classes_allocated = 0;
loadable_classes_used = 0;
// Call all +loads for the detached list.
for (i = 0; i < used; i++) {
Class cls = classes[i].cls;
load_method_t load_method = (load_method_t)classes[i].method;
if (!cls) continue;
if (PrintLoading) {
_objc_inform("LOAD: +[%s load]\n", cls->nameForLogging());
}
(*load_method)(cls, SEL_load);
}
// Destroy the detached list.
if (classes) free(classes);
}
4.2.1 call_category_loads 调用分类的load方法
static bool call_category_loads(void)
{
int i, shift;
bool new_categories_added = NO;
// Detach current loadable list.
struct loadable_category *cats = loadable_categories;
int used = loadable_categories_used;
int allocated = loadable_categories_allocated;
loadable_categories = nil;
loadable_categories_allocated = 0;
loadable_categories_used = 0;
// Call all +loads for the detached list.
for (i = 0; i < used; i++) {
Category cat = cats[i].cat;
load_method_t load_method = (load_method_t)cats[i].method;
Class cls;
if (!cat) continue;
cls = _category_getClass(cat);
if (cls && cls->isLoadable()) {
if (PrintLoading) {
_objc_inform("LOAD: +[%s(%s) load]\n",
cls->nameForLogging(),
_category_getName(cat));
}
(*load_method)(cls, SEL_load);
cats[i].cat = nil;
}
}
// Compact detached list (order-preserving)
shift = 0;
for (i = 0; i < used; i++) {
if (cats[i].cat) {
cats[i-shift] = cats[I];
} else {
shift++;
}
}
used -= shift;
// Copy any new +load candidates from the new list to the detached list.
new_categories_added = (loadable_categories_used > 0);
for (i = 0; i < loadable_categories_used; i++) {
if (used == allocated) {
allocated = allocated*2 + 16;
cats = (struct loadable_category *)
realloc(cats, allocated *
sizeof(struct loadable_category));
}
cats[used++] = loadable_categories[I];
}
// Destroy the new list.
if (loadable_categories) free(loadable_categories);
// Reattach the (now augmented) detached list.
// But if there's nothing left to load, destroy the list.
if (used) {
loadable_categories = cats;
loadable_categories_used = used;
loadable_categories_allocated = allocated;
} else {
if (cats) free(cats);
loadable_categories = nil;
loadable_categories_used = 0;
loadable_categories_allocated = 0;
}
if (PrintLoading) {
if (loadable_categories_used != 0) {
_objc_inform("LOAD: %d categories still waiting for +load\n",
loadable_categories_used);
}
}
return new_categories_added;
}
现在,我们可以对load方法的调用做下总结了:
调用顺序也就是跟我们上面说的调用规则一样了。
上面关于给分类设置关联对象的时候,我们给当前对象的isa设置了一个标志,这里做一下补充: