category
1、category的本質(zhì)
今天我們先從category講起,那到底什么是category,我們借助clang看一下它在底層的結(jié)構(gòu)。
代碼還是上一章的代碼,添加分類(lèi):
@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
打開(kāi)生成的.cpp文件,搜索LRPerson找到以下代碼

這是一個(gè)_category_t的結(jié)構(gòu),里面有一個(gè)LRPerson的名字,還有兩個(gè)_method_list_t:一個(gè)_CATEGORY_INSTANCE_METHODS_,一個(gè)_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;
};
這是一個(gè)結(jié)構(gòu)體,里面有6個(gè)變量
name : 類(lèi)名
cls:屬于哪個(gè)類(lèi)
instance_methods:實(shí)例方法
class_methods:類(lèi)方法
protocols:協(xié)議
properties:屬性
- 搜索
_CATEGORY_INSTANCE_METHODS_
分類(lèi)中實(shí)現(xiàn)的instanceMethod保存在這里,是一個(gè)method_t結(jié)構(gòu)
struct method_t {
SEL name;
const char *types;
MethodListIMP imp;
}
- 搜索
_CATEGORY_CLASS_METHODS_
分類(lèi)中實(shí)現(xiàn)的classMethod保存在這里,是一個(gè)method_t結(jié)構(gòu)
綜上所述,category在底層其實(shí)是一個(gè)_category_t結(jié)構(gòu)體,有6個(gè)變量。
attachToClass
上一篇文章分析完了prepareMethodLists,這里我們接著分析attachToClass。

通過(guò)斷點(diǎn)可以看到
LRPerson類(lèi)走的是最下面的方法,不是元類(lèi) 傳入的參數(shù)為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);
}
}
我們發(fā)現(xiàn)LRPerson類(lèi)根本就沒(méi)有走if里面的方法。

那么分類(lèi)是什么時(shí)候加載的呢?
category和類(lèi)的加載一樣,懶加載分類(lèi)和非懶加載分類(lèi)有一定的區(qū)別:
1.非懶加載類(lèi) + 非懶加載分類(lèi)
load_images -> loadAllCategories() -> load_categories_nolock -> attachCategories -> prepareMethodLists -> attachLists
2.懶加載類(lèi) + 非懶加載分類(lèi)
load_images -> prepare_load_methods -> realizeClassWithoutSwift -> methodizeClass -> attachToClass -> attachCategories -> prepareMethodLists -> attachLists
3.非懶加載類(lèi) + 懶加載分類(lèi)
直接寫(xiě)入mach-O
4.懶加載類(lèi) + 懶加載分類(lèi)
直接寫(xiě)入mach-O
1.非懶加載類(lèi) + 非懶加載分類(lèi)
@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
-
進(jìn)入
methodizeClass斷點(diǎn)
-
打印
list
此時(shí)category還沒(méi)有加載進(jìn)來(lái)。 -
斷點(diǎn)進(jìn)入
attachToClass方法
在這里打一個(gè)斷點(diǎn),LLDB調(diào)試
it和map.end()相等,并不會(huì)走以下條件。 -
進(jìn)入
attachCategories方法
在斷點(diǎn)處打印堆棧:

從堆棧可以看出是從
load_images -> loadAllCategories() -> load_categories_nolock -> attachCategories 進(jìn)來(lái)的

for循環(huán)category數(shù)組開(kāi)始處理
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 初始化
//開(kāi)始處理category數(shù)據(jù),為寫(xiě)到類(lèi)中做準(zhǔn)備
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數(shù)組的最后一位 mlists是一個(gè)二維數(shù)組,里面的元素還是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 從插入的最后位置到數(shù)組的最后位置
//排序的是mlists數(shù)組里的元素?cái)?shù)組
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);
}
這里穿插一個(gè)小細(xì)節(jié),對(duì)比下面兩張圖可以發(fā)現(xiàn)。編譯時(shí),category是以類(lèi)名LRPerson命名的,運(yùn)行時(shí)category名字變成了LR,并且cls已經(jīng)跟我們的LRPerson關(guān)聯(lián)起來(lái)了。


attachLists
void attachLists(List* const * addedLists, uint32_t addedCount) {
if (addedCount == 0) return;
if (hasArray()) {
//當(dāng)下面第三步 setArray()之后,再有新的list要插入就要走這個(gè)條件
// 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里面的元素移動(dòng)到后面
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));//將newArray里面的元素添加到數(shù)組前面
}
else if (!list && addedCount == 1) {
// 0 lists -> 1 list
// 一維數(shù)組
list = addedLists[0];
}
else {
// 1 list -> many lists
List* oldList = list;
uint32_t oldCount = oldList ? 1 : 0;
uint32_t newCount = oldCount + addedCount;//求出新數(shù)組容量
setArray((array_t *)malloc(array_t::byteSize(newCount)));//創(chuàng)建一個(gè)數(shù)組
array()->count = newCount;
if (oldList) array()->lists[addedCount] = oldList;//將oldList放到array()的最后一個(gè)位置
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));//從array()->lists的0號(hào)位置,放入addedLists,放入的大小是 addedCount * sizeof(array()->lists[0],也就是把a(bǔ)ddedLists平移放入array()數(shù)組,從0號(hào)位置開(kāi)始
}
}
category加入到rwe的流程是:
1.初始化rwe,給rwe賦值ro里的原始類(lèi)數(shù)據(jù)
2.調(diào)用attachLists,創(chuàng)建一個(gè)list保存baseMethods
3.當(dāng)有一個(gè)分類(lèi)時(shí),再次調(diào)用attachLists,走else方法。創(chuàng)建一個(gè)數(shù)組,將原來(lái)的baseMethods放到末尾,分類(lèi)里的數(shù)據(jù)放到前面。
4.再有分類(lèi)進(jìn)來(lái)時(shí),走if方法,創(chuàng)建新數(shù)組,將老數(shù)據(jù)放到末尾,新數(shù)據(jù)放前面。
5.還有分類(lèi)的話,重復(fù)第四步。
extAllocIfNeeded
attachCategories有一個(gè)取rwe的方法extAllocIfNeeded。這里簡(jiǎn)單看一下源碼
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.開(kāi)辟一個(gè)空間給rwe
2.設(shè)置rwe的version
3.從ro中獲取原始類(lèi)的baseMethods、baseProperties、baseProtocols
4.設(shè)置set_ro_or_rwe
2.懶加載類(lèi) + 非懶加載分類(lèi)
在attachCategories打上斷點(diǎn),等程序進(jìn)入斷點(diǎn)后,bt打印堆棧信息。

-
bt
從圖上可以看出,attachCategories是在load_images之后調(diào)用的。 搜索
load_images,打上斷點(diǎn),重新運(yùn)行程序

-
發(fā)現(xiàn)走入
prepare_load_methods
prepare_load_methods
void prepare_load_methods(const headerType *mhdr)
{
size_t count, i;
runtimeLock.assertLocked();
//獲取非懶加載的類(lèi)列表
classref_t const *classlist =
_getObjc2NonlazyClassList(mhdr, &count);
//為所有的非懶加載類(lèi)和他的父類(lèi)遞歸調(diào)用 +load方法
for (i = 0; i < count; i++) {
schedule_class_load(remapClass(classlist[i]));
}
//獲取非懶加載的分類(lèi)列表
category_t * const *categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
for (i = 0; i < count; i++) {
category_t *cat = categorylist[i];
Class cls = remapClass(cat->cls);//關(guān)聯(lián)上類(lèi)和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中加入以下代碼,打上斷點(diǎn)
//—— mark 2021-5-5
const char *mangledName = cls->mangledName();
const char *className = "LRPerson";
if (strcmp(mangledName, className) == 0){
printf("來(lái)了 \n");
}
//

跟蹤斷點(diǎn),進(jìn)入
realizeClassWithoutSwift
自realizeClassWithoutSwift之后,category后面的加載流程與第一種情況一致-
進(jìn)入
methodizeClass準(zhǔn)備好要插入的數(shù)據(jù)
-
進(jìn)入
attachToClass
進(jìn)入
attachCategories獲取method_list_t、property_list_t、protocol_list_t數(shù)據(jù),rwe在這之前初始化進(jìn)入
prepareMethodLists排序method_list_t進(jìn)入
attachLists寫(xiě)入到rwe
3.非懶加載類(lèi) + 懶加載分類(lèi)
在attachCategories打上斷點(diǎn),等程序進(jìn)入斷點(diǎn),再bt。
然而結(jié)果出人意料,程序根本沒(méi)有進(jìn)入斷點(diǎn)。然而我們?cè)诳刂婆_(tái),發(fā)現(xiàn)了一些信息,雖然沒(méi)有進(jìn)入attachCategories,但是他調(diào)用了我研究的其他三個(gè)方法。(這里有一些小失誤,沒(méi)有打印方法名)
我們?cè)?code>+load方法里打斷點(diǎn),再bt。


通過(guò)堆棧我們發(fā)現(xiàn),他并沒(méi)有走與
Category相關(guān)的方法。那
category的加載是否是在read_images時(shí)候呢?
首先在read_images里doneOnce的時(shí)候,加上以下代碼讀取一下Mach-O文件里的數(shù)據(jù),查看此時(shí)的baseMethods。

運(yùn)行程序,進(jìn)入斷點(diǎn)時(shí),打印
list:
我們意外的發(fā)現(xiàn),
baseMethods里的方法已經(jīng)有了category中重寫(xiě)的方法,而且他的總數(shù)count = 9,是我們LRPerson類(lèi)和category (LR)的總和。因此我們猜測(cè)category的加載,可能是在編譯期直接寫(xiě)入了mach-O文件。
4.懶加載類(lèi) + 懶加載分類(lèi)
同樣我們先查看mach-O文件的數(shù)據(jù)。

我們發(fā)現(xiàn)
baseMethods里的方法已經(jīng)有了category中重寫(xiě)的方法。
補(bǔ)充
前面只是舉例了單個(gè)category,多個(gè)category有點(diǎn)不一樣。
我們?cè)?code>新建一個(gè)LRPerson+LRB,重寫(xiě)instanceMethod`
- (void)instanceMethod {
NSLog(@"category----%s",__func__);
}
在attachCategories打斷點(diǎn)然后bt

1.非懶加載類(lèi) + 非懶加載分類(lèi)
read_images的時(shí)候LRB已經(jīng)寫(xiě)入了mach-O

區(qū)別在于加載第二個(gè)
category。attachCategories里的打印信息為:
load_images -> loadAllCategories() -> load_categories_nolock -> attachCategories
2.懶加載類(lèi) + 非懶加載分類(lèi)
read_images的時(shí)候LRB已經(jīng)寫(xiě)入了mach-O

attachCategories里的打印信息為:
load_images -> prepare_load_methods -> realizeClassWithoutSwift -> methodizeClass -> attachToClass -> attachCategories
3.非懶加載類(lèi) + 懶加載分類(lèi)
read_images的時(shí)候,兩個(gè)分類(lèi)LR和LRB已經(jīng)寫(xiě)入了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.懶加載類(lèi) + 懶加載分類(lèi)
read_images的時(shí)候,兩個(gè)分類(lèi)LR和LRB已經(jīng)寫(xiě)入了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)
}










