category
1、category的本质
今天我们先从category讲起,那到底什么是category,我们借助clang看一下它在底层的结构。
代码还是上一章的代码,添加分类:
@protocol LRPersonDelegate <NSObject>
- (void)delegateMethod;
@end
NS_ASSUME_NONNULL_BEGIN
@interface LRPerson (LR)
- (void)instanceMethod;
+ (void)classMethod;
@end
@implementation LRPerson (LR)
- (void)instanceMethod {
NSLog(@"category----%s",__func__);
}
+ (void)classMethod {
NSLog(@"category----%s",__func__);
}
@end
clang命令:clang -rewrite-objc main.m -o main.cpp
打开生成的.cpp文件,搜索LRPerson找到以下代码

这是一个_category_t的结构,里面有一个LRPerson的名字,还有两个_method_list_t:一个_CATEGORY_INSTANCE_METHODS_,一个_CATEGORY_CLASS_METHODS_
- 搜索
_category_t
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;
};
这是一个结构体,里面有6个变量
name : 类名
cls:属于哪个类
instance_methods:实例方法
class_methods:类方法
protocols:协议
properties:属性
- 搜索
_CATEGORY_INSTANCE_METHODS_
分类中实现的instanceMethod保存在这里,是一个method_t结构
struct method_t {
SEL name;
const char *types;
MethodListIMP imp;
}
- 搜索
_CATEGORY_CLASS_METHODS_
分类中实现的classMethod保存在这里,是一个method_t结构
综上所述,category在底层其实是一个_category_t结构体,有6个变量。
attachToClass
上一篇文章分析完了prepareMethodLists,这里我们接着分析attachToClass。

通过断点可以看到
LRPerson类走的是最下面的方法,不是元类 传入的参数为ATTACH_CLASS。
attachToClass
void attachToClass(Class cls, Class previously, int flags)
{
runtimeLock.assertLocked();
ASSERT((flags & ATTACH_CLASS) ||
(flags & ATTACH_METACLASS) ||
(flags & ATTACH_CLASS_AND_METACLASS));
auto &map = get();
auto it = map.find(previously);
if (it != map.end()) {
category_list &list = it->second;
if (flags & ATTACH_CLASS_AND_METACLASS) {
int otherFlags = flags & ~ATTACH_CLASS_AND_METACLASS;
attachCategories(cls, list.array(), list.count(), otherFlags | ATTACH_CLASS);
attachCategories(cls->ISA(), list.array(), list.count(), otherFlags | ATTACH_METACLASS);
} else {
attachCategories(cls, list.array(), list.count(), flags);
}
map.erase(it);
}
}
我们发现LRPerson类根本就没有走if里面的方法。

那么分类是什么时候加载的呢?
category和类的加载一样,懒加载分类和非懒加载分类有一定的区别:
1.非懒加载类 + 非懒加载分类
load_images -> loadAllCategories() -> load_categories_nolock -> attachCategories -> prepareMethodLists -> attachLists
2.懒加载类 + 非懒加载分类
load_images -> prepare_load_methods -> realizeClassWithoutSwift -> methodizeClass -> attachToClass -> attachCategories -> prepareMethodLists -> attachLists
3.非懒加载类 + 懒加载分类
直接写入mach-O
4.懒加载类 + 懒加载分类
直接写入mach-O
1.非懒加载类 + 非懒加载分类
@interface LRPerson : NSObject
@property (nonatomic,strong) NSString *name;
@property (nonatomic,strong) NSString *icon;
- (void)sayHello;
- (void)sayHappy;
- (void)instanceMethod;
+ (void)classMethod;
@end
@implementation LRPerson
//- (void)sayHello {
// NSLog(@"%@",_cmd);
//}
- (void)sayHappy {
NSLog(@"%s",__func__);
}
- (void)instanceMethod {
NSLog(@"%s",__func__);
}
+ (void)classMethod {
NSLog(@"%s",__func__);
}
+ (void)load {
NSLog(@"%s",__func__);
}
@end
@protocol LRPersonDelegate <NSObject>
- (void)delegateMethod;
@end
NS_ASSUME_NONNULL_BEGIN
@interface LRPerson (LR)
- (void)cateA;
@end
NS_ASSUME_NONNULL_END
@implementation LRPerson (LR)
- (void)instanceMethod {
NSLog(@"category----%s",__func__);
}
+ (void)classMethod {
NSLog(@"category----%s",__func__);
}
- (void)cateA{
NSLog(@"category----%s",__func__);
}
+ (void)load {
NSLog(@"category----%s",__func__);
}
@end
-
进入
methodizeClass断点
-
打印
list
此时category还没有加载进来。 -
断点进入
attachToClass方法
在这里打一个断点,LLDB调试
it和map.end()相等,并不会走以下条件。 -
进入
attachCategories方法
在断点处打印堆栈:

从堆栈可以看出是从
load_images -> loadAllCategories() -> load_categories_nolock -> attachCategories 进来的

for循环category数组开始处理
method_list_t、property_list_t、protocol_list_t。
static void
attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count,
int flags)
{
if (slowpath(PrintReplacedMethods)) {
printReplacements(cls, cats_list, cats_count);
}
if (slowpath(PrintConnecting)) {
_objc_inform("CLASS: attaching %d categories to%s class '%s'%s",
cats_count, (flags & ATTACH_EXISTING) ? " existing" : "",
cls->nameForLogging(), (flags & ATTACH_METACLASS) ? " (meta)" : "");
}
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();//rwe 初始化
//开始处理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) {
prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
rwe->methods.attachLists(mlists, mcount);
mcount = 0;
}
//将mlist插入mlists数组的最后一位 mlists是一个二维数组,里面的元素还是method_list_t
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) {
//排序mlists里的元素method_list_t 从插入的最后位置到数组的最后位置
//排序的是mlists数组里的元素数组
prepareMethodLists(cls, mlists + ATTACH_BUFSIZ - mcount, mcount, NO, fromBundle);
//attachLists把方法添加到列表里面
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);
}
这里穿插一个小细节,对比下面两张图可以发现。编译时,category是以类名LRPerson命名的,运行时category名字变成了LR,并且cls已经跟我们的LRPerson关联起来了。


attachLists
void attachLists(List* const * addedLists, uint32_t addedCount) {
if (addedCount == 0) return;
if (hasArray()) {
//当下面第三步 setArray()之后,再有新的list要插入就要走这个条件
// 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]));//将oldArray里面的元素移动到后面
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));//将newArray里面的元素添加到数组前面
}
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;//将oldList放到array()的最后一个位置
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));//从array()->lists的0号位置,放入addedLists,放入的大小是 addedCount * sizeof(array()->lists[0],也就是把addedLists平移放入array()数组,从0号位置开始
}
}
category加入到rwe的流程是:
1.初始化rwe,给rwe赋值ro里的原始类数据
2.调用attachLists,创建一个list保存baseMethods
3.当有一个分类时,再次调用attachLists,走else方法。创建一个数组,将原来的baseMethods放到末尾,分类里的数据放到前面。
4.再有分类进来时,走if方法,创建新数组,将老数据放到末尾,新数据放前面。
5.还有分类的话,重复第四步。
extAllocIfNeeded
attachCategories有一个取rwe的方法extAllocIfNeeded。这里简单看一下源码
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);
}
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;
}
主要流程:
1.开辟一个空间给rwe
2.设置rwe的version
3.从ro中获取原始类的baseMethods、baseProperties、baseProtocols
4.设置set_ro_or_rwe
2.懒加载类 + 非懒加载分类
在attachCategories打上断点,等程序进入断点后,bt打印堆栈信息。

-
bt
从图上可以看出,attachCategories是在load_images之后调用的。 搜索
load_images,打上断点,重新运行程序

-
发现走入
prepare_load_methods
prepare_load_methods
void prepare_load_methods(const headerType *mhdr)
{
size_t count, i;
runtimeLock.assertLocked();
//获取非懒加载的类列表
classref_t const *classlist =
_getObjc2NonlazyClassList(mhdr, &count);
//为所有的非懒加载类和他的父类递归调用 +load方法
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);//关联上类和category
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);
}
}
- 在
prepare_load_methods中加入以下代码,打上断点
//—— mark 2021-5-5
const char *mangledName = cls->mangledName();
const char *className = "LRPerson";
if (strcmp(mangledName, className) == 0){
printf("来了 \n");
}
//

跟踪断点,进入
realizeClassWithoutSwift
自realizeClassWithoutSwift之后,category后面的加载流程与第一种情况一致-
进入
methodizeClass准备好要插入的数据
-
进入
attachToClass
进入
attachCategories获取method_list_t、property_list_t、protocol_list_t数据,rwe在这之前初始化进入
prepareMethodLists排序method_list_t进入
attachLists写入到rwe
3.非懒加载类 + 懒加载分类
在attachCategories打上断点,等程序进入断点,再bt。
然而结果出人意料,程序根本没有进入断点。然而我们在控制台,发现了一些信息,虽然没有进入attachCategories,但是他调用了我研究的其他三个方法。(这里有一些小失误,没有打印方法名)
我们在+load方法里打断点,再bt。


通过堆栈我们发现,他并没有走与
Category相关的方法。那
category的加载是否是在read_images时候呢?
首先在read_images里doneOnce的时候,加上以下代码读取一下Mach-O文件里的数据,查看此时的baseMethods。

运行程序,进入断点时,打印
list:
我们意外的发现,
baseMethods里的方法已经有了category中重写的方法,而且他的总数count = 9,是我们LRPerson类和category (LR)的总和。因此我们猜测category的加载,可能是在编译期直接写入了mach-O文件。
4.懒加载类 + 懒加载分类
同样我们先查看mach-O文件的数据。

我们发现
baseMethods里的方法已经有了category中重写的方法。
补充
前面只是举例了单个category,多个category有点不一样。
我们再新建一个LRPerson+LRB,重写instanceMethod`
- (void)instanceMethod {
NSLog(@"category----%s",__func__);
}
在attachCategories打断点然后bt

1.非懒加载类 + 非懒加载分类
read_images的时候LRB已经写入了mach-O

区别在于加载第二个
category。attachCategories里的打印信息为:
load_images -> loadAllCategories() -> load_categories_nolock -> attachCategories
2.懒加载类 + 非懒加载分类
read_images的时候LRB已经写入了mach-O

attachCategories里的打印信息为:
load_images -> prepare_load_methods -> realizeClassWithoutSwift -> methodizeClass -> attachToClass -> attachCategories
3.非懒加载类 + 懒加载分类
read_images的时候,两个分类LR和LRB已经写入了mach-O
(lldb) p *list
warning: could not find Objective-C class data in the process. This may reduce the quality of type information available.
(method_list_t) $0 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 24
count = 10
first = {
name = "instanceMethod"
types = 0x0000000100000e55 "v16@0:8"
imp = 0x0000000100000cc0 (KCObjc`-[LRPerson(LR) instanceMethod])
}
}
}
(lldb) p $0.get(0)
(method_t) $1 = {
name = "instanceMethod"
types = 0x0000000100000e55 "v16@0:8"
imp = 0x0000000100000cc0 (KCObjc`-[LRPerson(LR) instanceMethod])
}
(lldb) p $0.get(1)
(method_t) $2 = {
name = "cateA"
types = 0x0000000100000e55 "v16@0:8"
imp = 0x0000000100000cf0 (KCObjc`-[LRPerson(LR) cateA])
}
(lldb) p $0.get(2)
(method_t) $3 = {
name = "instanceMethod"
types = 0x0000000100000e55 "v16@0:8"
imp = 0x0000000100000c60 (KCObjc`-[LRPerson(LRB) instanceMethod])
}
(lldb) p $0.get(3)
(method_t) $4 = {
name = "sayHappy"
types = 0x0000000100000e55 "v16@0:8"
imp = 0x0000000100000b20 (KCObjc`-[LRPerson sayHappy] at LRPerson.m:15)
}
(lldb) p $0.get(4)
(method_t) $5 = {
name = "instanceMethod"
types = 0x0000000100000e55 "v16@0:8"
imp = 0x0000000100000b50 (KCObjc`-[LRPerson instanceMethod] at LRPerson.m:18)
}
(lldb) p $0.get(5)
(method_t) $6 = {
name = ".cxx_destruct"
types = 0x0000000100000e55 "v16@0:8"
imp = 0x0000000100000b80 (KCObjc`-[LRPerson .cxx_destruct] at LRPerson.m:10)
}
(lldb) p $0.get(6)
(method_t) $7 = {
name = "name"
types = 0x0000000100000e69 "@16@0:8"
imp = 0x0000000100000bc0 (KCObjc`-[LRPerson name] at LRPerson.h:15)
}
(lldb) p $0.get(7)
(method_t) $8 = {
name = "setName:"
types = 0x0000000100000e71 "v24@0:8@16"
imp = 0x0000000100000be0 (KCObjc`-[LRPerson setName:] at LRPerson.h:15)
}
(lldb) p $0.get(8)
(method_t) $9 = {
name = "icon"
types = 0x0000000100000e69 "@16@0:8"
imp = 0x0000000100000c10 (KCObjc`-[LRPerson icon] at LRPerson.h:16)
}
(lldb) p $0.get(9)
(method_t) $10 = {
name = "setIcon:"
types = 0x0000000100000e71 "v24@0:8@16"
imp = 0x0000000100000c30 (KCObjc`-[LRPerson setIcon:] at LRPerson.h:16)
}
(lldb) p $0.get(10)
Assertion failed: (i < count), function get, file /Users/liuyang/Desktop/可编译objc源码/runtime/objc-runtime-new.h, line 438.
error: Execution was interrupted, reason: signal SIGABRT.
The process has been returned to the state before expression evaluation.
(lldb)
4.懒加载类 + 懒加载分类
read_images的时候,两个分类LR和LRB已经写入了mach-O
(lldb) p *list
warning: could not find Objective-C class data in the process. This may reduce the quality of type information available.
(method_list_t) $0 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 24
count = 10
first = {
name = "instanceMethod"
types = 0x0000000100000e60 "v16@0:8"
imp = 0x0000000100000cd0 (KCObjc`-[LRPerson(LR) instanceMethod])
}
}
}
(lldb) p $0.get(0)
(method_t) $1 = {
name = "instanceMethod"
types = 0x0000000100000e60 "v16@0:8"
imp = 0x0000000100000cd0 (KCObjc`-[LRPerson(LR) instanceMethod])
}
(lldb) p $0.get(1)
(method_t) $2 = {
name = "cateA"
types = 0x0000000100000e60 "v16@0:8"
imp = 0x0000000100000d00 (KCObjc`-[LRPerson(LR) cateA])
}
(lldb) p $0.get(2)
(method_t) $3 = {
name = "instanceMethod"
types = 0x0000000100000e60 "v16@0:8"
imp = 0x0000000100000c70 (KCObjc`-[LRPerson(LRB) instanceMethod])
}
(lldb) p $0.get(3)
(method_t) $4 = {
name = "sayHappy"
types = 0x0000000100000e60 "v16@0:8"
imp = 0x0000000100000b30 (KCObjc`-[LRPerson sayHappy] at LRPerson.m:15)
}
(lldb) p $0.get(4)
(method_t) $5 = {
name = "instanceMethod"
types = 0x0000000100000e60 "v16@0:8"
imp = 0x0000000100000b60 (KCObjc`-[LRPerson instanceMethod] at LRPerson.m:18)
}
(lldb) p $0.get(5)
(method_t) $6 = {
name = ".cxx_destruct"
types = 0x0000000100000e60 "v16@0:8"
imp = 0x0000000100000b90 (KCObjc`-[LRPerson .cxx_destruct] at LRPerson.m:10)
}
(lldb) p $0.get(6)
(method_t) $7 = {
name = "name"
types = 0x0000000100000e74 "@16@0:8"
imp = 0x0000000100000bd0 (KCObjc`-[LRPerson name] at LRPerson.h:15)
}
(lldb) p $0.get(7)
(method_t) $8 = {
name = "setName:"
types = 0x0000000100000e7c "v24@0:8@16"
imp = 0x0000000100000bf0 (KCObjc`-[LRPerson setName:] at LRPerson.h:15)
}
(lldb) p $0.get(8)
(method_t) $9 = {
name = "icon"
types = 0x0000000100000e74 "@16@0:8"
imp = 0x0000000100000c20 (KCObjc`-[LRPerson icon] at LRPerson.h:16)
}
(lldb) p $0.get(9)
(method_t) $10 = {
name = "setIcon:"
types = 0x0000000100000e7c "v24@0:8@16"
imp = 0x0000000100000c40 (KCObjc`-[LRPerson setIcon:] at LRPerson.h:16)
}










