应用程序加载(一) -- dyld流程分析
应用程序加载(二) -- dyld&objc关联以及类的加载初探
应用程序加载(三)-- 类的加载
应用程序加载(四)-- 分类的加载
应用程序加载(五)-- 类扩展和关联对象
1、分类的本质
分类在底层是一个category_t
结构体,我们可以通过clang
方式查看。也可以直接在objc
源码中搜索。
clang的方式:
struct _category_t {
const char *name;
struct _class_t *cls;
const struct _method_list_t *instance_methods;
const struct _method_list_t *class_methods;
const struct _protocol_list_t *protocols;
const struct _prop_list_t *properties;
};
源码中:
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);
protocol_list_t *protocolsForMeta(bool isMeta) {
if (isMeta) return nullptr;
else return protocols;
}
};
- name:分类的名字
- cls:对应的原类
- instanceMethods:实例方法列表
- classMethods:类方法列表
- protocols:协议列表
- instanceProperties:实例属性列表
2、分类的加载
分类的加载在_objc_init
里的_dyld_objc_notify_register
的第二个参数load_image
。因此我们从它作为研究的入口。
load_image
/***********************************************************************
* didInitialAttachCategories
* Whether the initial attachment of categories present at startup has
* been done.
**********************************************************************/
static bool didInitialAttachCategories = false;
/***********************************************************************
* load_images
* Process +load in the given images which are being mapped in by dyld.
*
* Locking: write-locks runtimeLock and loadMethodLock
**********************************************************************/
void
load_images(const char *path __unused, const struct mach_header *mh)
{
if (!didInitialAttachCategories && didCallDyldNotifyRegister) {
didInitialAttachCategories = true;
loadAllCategories();
}
// 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();
}
- 源码一进来就是一个
if
判断,其中由两个变量控制,分别是:didInitialAttachCategories
和didCallDyldNotifyRegister
-
didInitialAttachCategories
:对分类
只执行一次,默认值是false
。当进入判断后,设置为true
-
didCallDyldNotifyRegister
:是dyld
相关联的,默认值是false
,但是在objc_init
中设置为true
-
- 进入
if
中,调用了loadAllCategories
函数,看看它的实现 - 如果发现有
load
方法,就会调用prepare_load_methods
函数。源码实现:
void prepare_load_methods(const headerType *mhdr)
{
size_t count, i;
runtimeLock.assertLocked();
classref_t const *classlist =
_getObjc2NonlazyClassList(mhdr, &count);
for (i = 0; i < count; i++) {
schedule_class_load(remapClass(classlist[i]));
}
category_t * const *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
if (cls->isSwiftStable()) {
_objc_fatal("Swift class extensions and categories on Swift "
"classes are not allowed to have +load methods");
}
realizeClassWithoutSwift(cls, nil);
ASSERT(cls->ISA()->isRealized());
add_category_to_loadable_list(cat);
}
}
- 此处获取非懒加载分类,也就是实现了load方法的分类。调用
realizeClassWithoutSwift
函数,目的是处理类没有实现load
方法时,分类会间接去实现当前的类。
loadAllCategories
static void loadAllCategories() {
mutex_locker_t lock(runtimeLock);
for (auto *hi = FirstHeader; hi != NULL; hi = hi->getNext()) {
load_categories_nolock(hi);
}
}
static void load_categories_nolock(header_info *hi) {
bool hasClassProperties = hi->info()->hasCategoryClassProperties();
size_t count;
auto processCatlist = [&](category_t * const *catlist) {
for (unsigned i = 0; i < count; i++) {
category_t *cat = catlist[i];
Class cls = remapClass(cat->cls);
locstamped_category_t lc{cat, hi};
if (!cls) {
// Category's target class is missing (probably weak-linked).
// Ignore the category.
if (PrintConnecting) {
_objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with "
"missing weak-linked target class",
cat->name, cat);
}
continue;
}
// Process this category.
if (cls->isStubClass()) {
// Stub classes are never realized. Stub classes
// don't know their metaclass until they're
// initialized, so we have to add categories with
// class methods or properties to the stub itself.
// methodizeClass() will find them and add them to
// the metaclass as appropriate.
if (cat->instanceMethods ||
cat->protocols ||
cat->instanceProperties ||
cat->classMethods ||
cat->protocols ||
(hasClassProperties && cat->_classProperties))
{
objc::unattachedCategories.addForClass(lc, cls);
}
} else {
// First, register the category with its target class.
// Then, rebuild the class's method lists (etc) if
// the class is realized.
if (cat->instanceMethods || cat->protocols
|| cat->instanceProperties)
{
if (cls->isRealized()) {//类已经实现
attachCategories(cls, &lc, 1, ATTACH_EXISTING);
} else {
objc::unattachedCategories.addForClass(lc, cls);
}
}
if (cat->classMethods || cat->protocols
|| (hasClassProperties && cat->_classProperties))
{
if (cls->ISA()->isRealized()) {//元类已经实现
attachCategories(cls->ISA(), &lc, 1, ATTACH_EXISTING | ATTACH_METACLASS);
} else {
objc::unattachedCategories.addForClass(lc, cls->ISA());
}
}
}
}
};
processCatlist(_getObjc2CategoryList(hi, &count));
processCatlist(_getObjc2CategoryList2(hi, &count));
}
- 源码大意是判断类是否已经实现,实现的情况下直接调用
attachCategories
。还没有实现时,就先把分类中的数据保存一下,后续再做一些操作。
注意:类为什么会没有实现呢?
- 分类也有懒加载和非懒加载两中情况!与类相同,都取决于有没有实现
+load
方法。 - 如果类是懒加载方式(没有
load
),而分类是非懒加载方式(有load
),这样当加载分类的时候,就会出现类还没有实现的情况。 - 文章后续会对类和分类,分别有或者没有
load
时的区别进行解释。
attachCategories
static void
attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count,
int flags)
{
//省略不关心的代码
...
constexpr uint32_t ATTACH_BUFSIZ = 64;
method_list_t *mlists[ATTACH_BUFSIZ];
property_list_t *proplists[ATTACH_BUFSIZ];
protocol_list_t *protolists[ATTACH_BUFSIZ];
uint32_t mcount = 0;
uint32_t propcount = 0;
uint32_t protocount = 0;
bool fromBundle = NO;
bool isMeta = (flags & ATTACH_METACLASS);
//rwe的创建
auto rwe = cls->data()->extAllocIfNeeded();
// 整理category中的数据
for (uint32_t i = 0; i < cats_count; i++) {
auto& entry = cats_list[i];
method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
if (mlist) {
if (mcount == ATTACH_BUFSIZ) {//ATTACH_BUFSIZ=64,一种饱和策略,通常不会进
prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
rwe->methods.attachLists(mlists, mcount);
mcount = 0;
}
mlists[ATTACH_BUFSIZ - ++mcount] = mlist;
fromBundle |= entry.hi->isBundle();
}
property_list_t *proplist =
entry.cat->propertiesForMeta(isMeta, entry.hi);
if (proplist) {
if (propcount == ATTACH_BUFSIZ) {
rwe->properties.attachLists(proplists, propcount);
propcount = 0;
}
proplists[ATTACH_BUFSIZ - ++propcount] = proplist;
}
protocol_list_t *protolist = entry.cat->protocolsForMeta(isMeta);
if (protolist) {
if (protocount == ATTACH_BUFSIZ) {
rwe->protocols.attachLists(protolists, protocount);
protocount = 0;
}
protolists[ATTACH_BUFSIZ - ++protocount] = protolist;
}
}
//加入到类中
if (mcount > 0) {
//排序
prepareMethodLists(cls, mlists + ATTACH_BUFSIZ - mcount, mcount, NO, fromBundle);
//向rwe的方法列表中添加
rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);
if (flags & ATTACH_EXISTING) flushCaches(cls);
}
rwe->properties.attachLists(proplists + ATTACH_BUFSIZ - propcount, propcount);
rwe->protocols.attachLists(protolists + ATTACH_BUFSIZ - protocount, protocount);
}
- 开辟rwe的空间,然后获取分类中的方法、属性、协议这些信息,保存到rwe中。
- 保存方法调用的是
attachLists
attachLists
void attachLists(List* const * addedLists, uint32_t addedCount) {
if (addedCount == 0) return;
if (hasArray()) {
// n到n+m个
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到1个
list = addedLists[0];
}
else {
//0或者1到n个
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; // 4
if (oldList) array()->lists[addedCount] = oldList;
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
}
- 整个源码就是一个扩容保存的算法。但是有三种情况:
-
if
阶段:也是相对比较复杂的阶段。多到多的过程,二维数组的情况。例如:方法列表中已经有类的方法列表和一个分类的方法列表。此时再进入另一个分类的方法列表,就会在这个阶段进行处理。 -
else if
阶段:比较简单,就是0到1的过程。就是类的方法列表进行保存。 -
else
阶段,就是0或1到n的阶段。类的方法已经存在,往里添加一个分类的方法列表。 - 而且整个过程中,都是后进来的放在前面,原始数据后移。这也是为什么分类方法会在原始方法的前面。当我们调用同名方法时,会优先调用分类中的。
- 如果有多个分类中有想同的方法,那么肯定是调用后进来的。这也就是和分类文件的编译顺序有关。
-
目前已经了解分类中的信息(方法、属性、协议)是如果加载到rwe
中,但是没有看到类中的信息是如何加载进去的。上面的源码中,rwe
是刚刚开辟的空间,然后就进行了分类信息的加入。根本没有看到类信息的在什么时候加载到rwe
中的。所以我们还需要看看rwe
在开辟的时候做了什么处理!
extAllocIfNeeded
auto rwe = cls->data()->extAllocIfNeeded();
⏬⏬⏬
class_rw_ext_t *extAllocIfNeeded() {
auto v = get_ro_or_rwe();
if (fastpath(v.is<class_rw_ext_t *>())) {
return v.get<class_rw_ext_t *>();
} else {
return extAlloc(v.get<const class_ro_t *>());
}
}
⏬⏬⏬
class_rw_ext_t *
class_rw_t::extAlloc(const class_ro_t *ro, bool deepCopy)
{
runtimeLock.assertLocked();
auto rwe = objc::zalloc<class_rw_ext_t>();
rwe->version = (ro->flags & RO_META) ? 7 : 0;
method_list_t *list = ro->baseMethods();
if (list) {
if (deepCopy) list = list->duplicate();
rwe->methods.attachLists(&list, 1);
}
// See comments in objc_duplicateClass
// property lists and protocol lists historically
// have not been deep-copied
//
// This is probably wrong and ought to be fixed some day
property_list_t *proplist = ro->baseProperties;
if (proplist) {
rwe->properties.attachLists(&proplist, 1);
}
protocol_list_t *protolist = ro->baseProtocols;
if (protolist) {
rwe->protocols.attachLists(&protolist, 1);
}
set_ro_or_rwe(rwe, ro);
return rwe;
}
- 通过源码看到在开辟空间后,将类中的
ro
获取相关的类信息,进行了attachLists
处理。 - 这一步就是
attachLists
中的0到1的过程。
分类加载流程图
通过上面源码的分析,我们了解到整个分类的加载过程,简要的流程图如下:
3、load方法实现情况对类和分类加载情况的影响
这部分内容会结合类和分类的加载情况,不了解类加载的请先查看本人的上一篇文章:应用程序加载(三)-- 类的加载
类和分类在分别实现或者不实现load
方法的时候,对加载时机是有一定影响的,可以分为四种情况
类 | 分类 | |
---|---|---|
1 | 实现load方法 | 实现load方法 |
2 | 不实现load方法 | 实现load方法 |
3 | 实现load方法 | 不实现load方法 |
4 | 不实现load方法 | 不实现load方法 |
第一种情况,都实现load方法
这种情况类和分类都属于非加载的情况,大致流程如下:
- 首先对类进行加载,然后对把分类的信息贴到类中。
第二种情况,类不实现,分类实现
懒加载类和非懒加载分类的方式。这种情况在_read_images
中就不会对类做实现操作,就由分类来出发类的实现。也就是入口变成load_images
第三种情况,类实现,分类不实现
类是非懒加载类,分类是懒加载。
此时只处理类的相关信息。而分类的相关信息,在编译时期已经和类“绑定”一起。
第四种情况,都不实现
类和分类都是懒加载的方式,这种情况上篇文章说过。此时实现类的时机在第一次实例方法调用的时候,通常都是alloc
方法时。这个和第三种情况一样,都是在编译时期类和分类的信息已经“绑定”一起了。