所用版本:
- 处理器: Intel Core i9
- MacOS 12.3.1
- Xcode 13.3.1
- objc4-838
建议先看下
OC底层探索(十三): 类的加载(一)
OC底层探索(十四): 类的加载(二)
OC底层探索(十五): 类的加载(三)
看下分类的加载情况, 我们创建一个分类, 断点进行看一下。创建分类可参考: OC 创建分类
分类的加载
分类加载有4种情况,
- 分类, 主类都有
+load
方法 - 分类有
+load
方法, 主类无 - 主类有
+load
方法, 分类无 - 两者皆无
针对这四种情况, 我们依次探索下分类加载链
分类, 主类都有 +load
方法
-
load_images
→loadAllCategories
→load_categories_nolock
→attachCategories
跟断点走可发现, 并没有出现在_dyld_objc_notify_register(&map_images, load_images, unmap_image);
第一个参数 &map_images
去处理, 而是在load_images
中进行。

看打印信息或者走断点也能看出map_images
链先于load_images
完成.

当然我们也为了确保是我们创建的分类进入, 断点读取下, 也可发现类型为我们自定义分类"SRTestCategory"。 因为有+load, 当前类已经变为非懒加载类, 所以会走if (cls->isRealized())
判断中的attachCategories
。 关于load_categories_nolock
方法
load_categories_nolock
static void load_categories_nolock(header_info *hi) {
// header_info: Images in the shared cache will have an empty array here while those
// allocated at run time will allocate a single entry.
// 在共享缓存中的镜像, 将有一个空数组,而在运行时分配的将分配一个条目。
// ...
// header_info_rw rw_data[];
//} header_info;
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);
//将cat和hi包装成 locstamped_category_t
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.
// Stub classes 存根类不会被实现.
// 存根类在初始化之前不知道其元类,
// 因此我们必须向存根本身添加具有类方法或属性的类别。
// methodizeClass()将找到它们,并根据需要将它们添加到元类中。
// 其中: category_t *cat = catlist[I];
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.
// 首先,注册分类在其目标类。
// 然后,如果类已实现,则重建该类的方法列表(etc)。
// 实例
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());
}
}
}
}
};
// 处理Catlist, 实际是从mach-o中__objc_catlist, __objc_catlist2获取分类
// GETSECT(_getObjc2CategoryList, category_t * const, "__objc_catlist");
// GETSECT(_getObjc2CategoryList2, category_t * const, "__objc_catlist2");
// 闭包的调用
processCatlist(hi->catlist(&count));
processCatlist(hi->catlist2(&count));
}
- 先留意下这个外层
auto processCatlist = [&](category_t * const *catlist) { }
, 是一个闭包, 我们也可以当成block. 调用是最后2句
// 闭包的调用
processCatlist(hi->catlist(&count));
processCatlist(hi->catlist2(&count));
类似以调用block(实际上是调用闭包), &count
这个参数是传进去的。执行顺序也是先 processCatlist(hi->catlist(&count));
再执行processCatlist
里面的操作。emmm......话说苹果为何单写一个方法分出去 ( -_- ! )
接着看下闭包内部
- 现有
category_t
。
category_t
关于category_t
, 也可以通过Clang
命令转成cpp
文件查看底部实际执行
clang -rewrite-objc 分类.m

可看到包含:
- 名字
- 所属类
- 实例方法列表
- 类方法列表
- 协议
- 属性(无set, get 方法)


因为我添加了3个实例方法1个类方法, 看底层也能看出分类没有元类那套概念。关于属性, 我们新增一个属性看下。


可看出虽然写了属性, 但是系统并不会自动创建set
, get
方法。所以想给分类添加属性还得靠关联对象
方式。
-
关于
processCatlist
和header_info
数据结构
processCatlist数据结构
-
关于
cat
数据结构
cat数据结构
其实load_categories_nolock
主要做了
- 定义一个闭包的分类列表
processCatlist
- 外层调用,
mach-o
中取数据, 按照 规定 处理好的分类信息加进processCatlist
那么是怎样个规定 添加的, 继续跟断点走到attachCategories

attachCategories
进入attachCategories

// Attach method lists and properties and protocols from categories to a class.
// Assumes the categories in cats are all loaded and sorted by load order,
// oldest categories first.
// 将分类方法列表/属性/协议搬到类中.
// 假设分类在cats中, 都是按加载顺序加载和排序的,旧分类优先
static void
attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count,
int flags)
{
...
/*
* Only a few classes have more than 64 categories during launch.
* This uses a little stack, and avoids malloc.
*
* Categories must be added in the proper order, which is back
* to front. To do that with the chunking, we iterate cats_list
* from front to back, build up the local buffers backwards,
* and call attachLists on the chunks. attachLists prepends the
* lists, so the final result is in the expected order.
* 只有少数分类在发布期间超过64个类别。
* 这使用栈,避免了malloc。
* 分类必须按从后到前的正确顺序添加。
* 为了实现拆分,我们从前到后迭代cats_list,向后构建本地缓冲区,并调用块上的attachLists。
* attachLists 方法预先列出了list,最终结果按预期顺序排列。
*/
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);
auto rwe = cls->data()->extAllocIfNeeded();
for (uint32_t i = 0; i < cats_count; i++) {
auto& entry = cats_list[I];
method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
if (mlist) {
// 判断有没有超出最大64
// constexpr uint32_t ATTACH_BUFSIZ = 64;
if (mcount == ATTACH_BUFSIZ) {
prepareMethodLists(cls, mlists, mcount, NO, fromBundle, __func__);
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, __func__);
rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);
...
}
rwe->properties.attachLists(proplists + ATTACH_BUFSIZ - propcount, propcount);
rwe->protocols.attachLists(protolists + ATTACH_BUFSIZ - protocount, protocount);
}
-
cls
中获取rwe
,auto rwe = cls->data()->extAllocIfNeeded();
-
rwe
将分类 方法 / 属性 / 协议 添加进主类方法中
拿方法列表举例跟一遍流程:

- 初始
uint32_t mcount = 0;
,constexpr uint32_t ATTACH_BUFSIZ = 64;
- 循环取
method_list_t
, 留意下是个*
即地址 - 插入mlists,
mlists[ATTACH_BUFSIZ - ++mcount] = mlist;
. 首次 mlists[64 - (0 + 1)] = mlists[63] = mlist
验证一下


mlist
是我们分类列表, 里面3个方法, 地址: 0x0000000100008028, 看下赋值之后mlists
最后一个mlist[63]也为0x0000000100008028

extAllocIfNeeded
关于取rwe
方法 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 *>(&ro_or_rw_ext);
} else {
return extAlloc(v.get<const class_ro_t *>(&ro_or_rw_ext));
}
}
class_rw_ext_t *
class_rw_t::extAlloc(const class_ro_t *ro, bool deepCopy)
{
runtimeLock.assertLocked();
// 开辟内存空间, 底层还是alloc
auto rwe = objc::zalloc<class_rw_ext_t>();
// 设置版本 元类-7, 非元类-0
rwe->version = (ro->flags & RO_META) ? 7 : 0;
// 获取ro方法列表
method_list_t *list = ro->baseMethods;
if (list) {
if (deepCopy) list = list->duplicate();
// 将ro的方法列表放入rwe中。
rwe->methods.attachLists(&list, 1);
}
// 获取属性
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);
}
//设置rwe, rwe->ro = ro;
set_ro_or_rwe(rwe, ro);
return rwe;
}
void set_ro_or_rwe(class_rw_ext_t *rwe, const class_ro_t *ro) {
// the release barrier is so that the class_rw_ext_t::ro initialization
// is visible to lockless readers
rwe->ro = ro;
ro_or_rw_ext_t{rwe, &ro_or_rw_ext}.storeAt(ro_or_rw_ext, memory_order_release);
}
- 通过
zalloc
创建rwe
。但是实际还是调用alloc
创建内存空间
template<class T>
T *zalloc()
{
return Zone<T, sizeof(T) % MALLOC_ALIGNMENT == 0>::alloc();
}
- rwe获取方法/属性/协议列表
- 设置rwe, 令
rwe->ro = ro;
接着往下走, 走到prepareMethodLists
和rwe赋值地方, 其中mlists + ATTACH_BUFSIZ - mcount
实际上是内存地址平移, mlists + 64 - 1 = 首地址 + 63 平移即新添加的mslit的指针的地址(指针的指针) 进入下一个关键方法attachLists
attachLists
void attachLists(List* const * addedLists, uint32_t addedCount) {
// 没有新增直接返回,
// 话说addedCount = 0 无需新增, 之前直接不调用不就好了么 :)
if (addedCount == 0) return;
if (hasArray()) {
// many lists -> many lists
// 定义一些初始值
uint32_t oldCount = array()->count;
uint32_t newCount = oldCount + addedCount;
array_t *newArray = (array_t *)malloc(array_t::byteSize(newCount));
newArray->count = newCount;
array()->count = newCount;
// 将旧的数组插入新的数组中。
// 从留意下: i 初始: oldCount - 1 从后往前插入
for (int i = oldCount - 1; i >= 0; I--)
newArray->lists[i + addedCount] = array()->lists[I];
// 新数组插在旧数组前面
// 从前往后插入 addedLists
for (unsigned i = 0; i < addedCount; I++)
newArray->lists[i] = addedLists[I];
// 释放旧数组, 设置新数组
free(array());
setArray(newArray);
validate();
}
else if (!list && addedCount == 1) {
// 本类没有方法的时候走这个逻辑
// 0 lists -> 1 list
list = addedLists[0];
validate();
}
else {
// 1 list -> many lists
Ptr<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;
// 如果oldList存在,array[endIndex] 最后一个元素为 oldList指针。
if (oldList) array()->lists[addedCount] = oldList;
// 循环将新加入的放到list前面。从前往后一个一个放。留意下这里都是指针。
for (unsigned i = 0; i < addedCount; I++)
array()->lists[i] = addedLists[I];
validate();
}
}
留意下 addedLists
实际操作的指针, 是**
这样一个结构, 看图

addedLists
实际插入的是list
的指针
, 如果这个时候又有新的addedLists
插入, 则有

综上, 主类与分类都有+load
分类加载情况执行:
-
load_images
→loadAllCategories
→load_categories_nolock
→attachCategories
分类有 +load
方法, 当前类无
主类有 +load
方法, 分类无
这个2中情况是一样的
map_images
→ _read_images
→ realizeClassWithoutSwift
分类已在data()
中完成加载。首先, 这2种情况, 主类类都变成了非懒加载类()
-
主类有 +load 方法, 分类无
: 变懒加载 -
分类有 +load 方法, 主类无
: 分类变懒加载, 主类同时也变懒加载
验证一下




realizeClassWithoutSwift
方法读取下ro
(留意下要ro
赋值完成), 读取其方法列表之后我们能发现, 分类在realizeClassWithoutSwift
之前已经有了, 系统编译时在data()
中早已完成处理 (mach-o
中就已处理好 )。
主分类两者皆无
类alloc
→ objc_alloc
→ lookupimpOrForward
→ realizeClassWithoutSwift
→ methodizeClass
推迟到主类alloc之后, 第一次消息发送时候进行, 数据已经在data()
中(mach -o已经给处理好了)。验证一下

多分类情况都有+load
- 主类实现
+load
:- 1个分类含有
+load
:load_images
→loadAllCategories
→load_categories_nolock
→attachCategories
(map_images链正常走)
- 1个分类含有
举例验证一下:


建立4个分类, 其中
主类
和SRTestCategoryC
加了 +load
, 顺序最好也留意一下, 运行:


首先在realizeClassWithoutSwift
中 ro
中读取下信息发现, 说明data()
中没处理分类


在load_image
链可以发现处理了, 在load_categories_nolock
中打印cat
信息, 可以发现有分类信息, 接下来会走attachCategories
去处理添加分类。但是留意下会发现, 为什么没有+load
的分类SRTestCategoryC
呢? 继续运行

可发现, 在第二次循环中才处理SRTestCategoryC
相关。 就你叫夏洛啊(就你有+load
), 好, 单独处理处理你。之后继续走attachCategories
。有类方法的话类方法的attachCategories
也走。
-
主类实现
+load
:- 多个分类含有
+load
:load_images
→loadAllCategories
→load_categories_nolock
→attachCategories
(map_images正常走)
- 多个分类含有
举例验证一下:


建立4个分类, 其中
主类
和SRTestCategoryA
, SRTestCategoryC
加了 +load
, 顺序最好也留意一下, 运行:


跟单个加分类类似, 加载分类在load_images
链, processCatlist
中循环添加, 先处理所有不含+load分类, 在依次处理+load分类
-
主类不实现
+load
:- 分类1个含有
+load
:map_images
→_read_images
→realizeClassWithoutSwift
跟之前类似mach-o系统已经处理, 存在data()中。
- 分类1个含有

- 多个分类含有
+load
:load_images
→loadAllCategories
→load_categories_nolock
→objc::unattachedCategories.addForClass
(map_images不正常走)
主类实现链: map_images
→ _read_images
→ load_images
→ prepare_load_methods
→ prepare_load_methods
→
realizeClassWithoutSwift
→ methodizeClass


跟主类实现链路类似, 最后分类走懒加载方法objc::unattachedCategories.addForClass
但是留意下, 此时主类链会在map_images
→ _read_images
→ load_images
→ prepare_load_methods
→ prepare_load_methods
→
realizeClassWithoutSwift
→ methodizeClass



读一下realizeClassWithoutSwift
中的ro
, 可发现只有主类相关, 没有分类
总结
分类加载链路情况:
- 主类实现
+load
:-
没有分类实现 :
map_images
→_read_images
→realizeClassWithoutSwift
-
一个分类实现 :
load_images
→loadAllCategories
→load_categories_nolock
→attachCategories
-
多个分类实现 :
load_images
→loadAllCategories
→load_categories_nolock
→attachCategories
-
- 主类不实现
+load
:-
没有分类实现 :
类alloc
→objc_alloc
→lookupimpOrForward
→realizeClassWithoutSwift
→methodizeClass
-
一个分类实现 :
+load
:map_images
→_read_images
→realizeClassWithoutSwift
-
多个分类实现 :
load_images
→loadAllCategories
→load_categories_nolock
→objc::unattachedCategories.addForClass
-

分类存放情况:
- 主类实现
+load
:- 没有分类实现 :
ro
,rwe
- 一个分类实现 :
rwe
,ro
无 - 多个分类实现 :
rwe
,ro
无
- 没有分类实现 :
- 主类不实现
+load
:- 没有分类实现 :
ro
,rwe
- 一个分类实现 :
ro
,rwe
- 多个分类实现 :
rwe
,ro
无
- 没有分类实现 :
网友评论