所用版本:
- 处理器: 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链
看打印信息或者走断点也能看出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
cpp
可看到包含:
- 名字
- 所属类
- 实例方法列表
- 类方法列表
- 协议
- 属性(无set, get 方法)
分类实例方法
实例/类方法
因为我添加了3个实例方法1个类方法, 看底层也能看出分类没有元类那套概念。关于属性, 我们新增一个属性看下。
分类属性
分类属性
可看出虽然写了属性, 但是系统并不会自动创建set, get方法。所以想给分类添加属性还得靠关联对象方式。
-
关于
processCatlist和header_info数据结构
processCatlist数据结构
-
关于
cat数据结构
cat数据结构
其实load_categories_nolock主要做了
- 定义一个闭包的分类列表
processCatlist - 外层调用,
mach-o中取数据, 按照 规定 处理好的分类信息加进processCatlist
那么是怎样个规定 添加的, 继续跟断点走到attachCategories
attachCategories
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将分类 方法 / 属性 / 协议 添加进主类方法中
拿方法列表举例跟一遍流程:
mlist
- 初始
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
image.png
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插入, 则有
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个分类
顺序
建立4个分类, 其中
主类和SRTestCategoryC加了 +load, 顺序最好也留意一下, 运行:
realizeClassWithoutSwift
realizeClassWithoutSwift
首先在realizeClassWithoutSwift中 ro中读取下信息发现, 说明data()中没处理分类
load_categories_nolock
load_categories_nolock
在load_image链可以发现处理了, 在load_categories_nolock中打印cat信息, 可以发现有分类信息, 接下来会走attachCategories去处理添加分类。但是留意下会发现, 为什么没有+load的分类SRTestCategoryC呢? 继续运行
load_categories_nolock
可发现, 在第二次循环中才处理SRTestCategoryC相关。 就你叫夏洛啊(就你有+load), 好, 单独处理处理你。之后继续走attachCategories。有类方法的话类方法的attachCategories也走。
-
主类实现
+load:- 多个分类含有
+load:load_images→loadAllCategories→load_categories_nolock→attachCategories(map_images正常走)
- 多个分类含有
举例验证一下:
4个分类
顺序
建立4个分类, 其中
主类和SRTestCategoryA, SRTestCategoryC加了 +load, 顺序最好也留意一下, 运行:
验证
验证
跟单个加分类类似, 加载分类在load_images链, processCatlist中循环添加, 先处理所有不含+load分类, 在依次处理+load分类
-
主类不实现
+load:- 分类1个含有
+load:map_images→_read_images→realizeClassWithoutSwift跟之前类似mach-o系统已经处理, 存在data()中。
- 分类1个含有
验证, 读的 realizeClassWithoutSwift 中 ro
- 多个分类含有
+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
懒加载添加
addForClass
跟主类实现链路类似, 最后分类走懒加载方法objc::unattachedCategories.addForClass
但是留意下, 此时主类链会在map_images → _read_images → load_images → prepare_load_methods → prepare_load_methods →
realizeClassWithoutSwift → methodizeClass
map链路区别
map链路区别
map链路区别
读一下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无
- 没有分类实现 :











网友评论