定義
category 的主要作用是為已經(jīng)存在的類添加方法黍檩。
官網(wǎng)提供了其他的優(yōu)勢:
- 可以把類的實現(xiàn)分開在幾個不同的文件里面颂碧。
- 可以減少分開文件的體積
- 可以把不同的功能組織到不同的category里
- 可以由多個開發(fā)者共同完成一個類
- 可以按需加載想要的類別等等。
- 聲明專有方法
原理
思考沫换?
一個類的實例方法臭蚁、類方法默認(rèn)都是存在這個類的類對象和元類對象中,那么分類的方法存在哪呢苗沧?
(也在類方法刊棕,合并,運行時合并 利用runtime)
首先我們創(chuàng)建一個類待逞,然后創(chuàng)建一個類的 category 文件甥角,并使用編譯命令來生成對應(yīng)的 c++ 文件。
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc Person+name.m
可以看見我們的分類被編譯成了下面這種結(jié)構(gòu)
// 空的分類
static struct _category_t _OBJC_$_CATEGORY_Person_$_name __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
"Person",
0, // &OBJC_CLASS_$_Person,
0,
0,
0,
0,
};
// 設(shè)置了方法 和 屬性等 編譯后的文件
static struct _category_t _OBJC_$_CATEGORY_Person_$_name __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
"Person",
0, // &OBJC_CLASS_$_Person,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_name,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_Person_$_name,
(const struct _protocol_list_t *)&_OBJC_CATEGORY_PROTOCOLS_$_Person_$_name,
(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_Person_$_name,
};
結(jié)構(gòu)體的類型為 _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; // 協(xié)議
const struct _prop_list_t *properties; // 屬性
};
實例方法的結(jié)構(gòu)體
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[1];
} _OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_name __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"myName", "@16@0:8", (void *)_I_Person_name_myName}}
};
類方法的結(jié)構(gòu)體
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[1];
} _OBJC_$_CATEGORY_CLASS_METHODS_Person_$_name __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"myHeight", "i16@0:8", (void *)_C_Person_name_myHeight}}
};
屬性的結(jié)構(gòu)體
static struct /*_prop_list_t*/ {
unsigned int entsize; // sizeof(struct _prop_t)
unsigned int count_of_properties;
struct _prop_t prop_list[1];
} _OBJC_$_PROP_LIST_Person_$_name __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_prop_t),
1,
{{"len","Ti,N"}} // i 指的是 int 類型
};
協(xié)議的結(jié)構(gòu)體
static struct /*_protocol_list_t*/ {
long protocol_count; // Note, this is 32/64 bit
struct _protocol_t *super_protocols[2];
} _OBJC_CATEGORY_PROTOCOLS_$_Person_$_name __attribute__ ((used, section ("__DATA,__objc_const"))) = {
2,
&_OBJC_PROTOCOL_NSCopying,
&_OBJC_PROTOCOL_NSCoding
};
runtime 源碼解讀
首先找到 runtime
的入口, 在 objc-os.mm
中的 _objc_init
函數(shù)
void _objc_init(void)
{
static bool initialized = false;
if (initialized) return;
initialized = true;
// fixme defer initialization until an objc-using image is found?
environ_init();
tls_init();
static_init();
runtime_init();
exception_init();
#if __OBJC2__
cache_t::init();
#endif
_imp_implementationWithBlock_init();
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
#if __OBJC2__
didCallDyldNotifyRegister = true;
#endif
}
然后在 map_images
中調(diào)用 map_images_nolock
,然后調(diào)用了_read_images
识樱,
然后里面有下面代碼
// Discover categories. Only do this after the initial category
// attachment has been done. For categories present at startup,
// discovery is deferred until the first load_images call after
// the call to _dyld_objc_notify_register completes. rdar://problem/53119145
if (didInitialAttachCategories) {
for (EACH_HEADER) {
load_categories_nolock(hi);
}
}
跟進(jìn)去之后嗤无,可以找到 attachCategories
方法,這部分代碼就是合并分類的代碼。
// 附加上分類的核心操作
// cls:類對象或者元類對象怜庸,cats_list:分類列表
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)" : "");
}
// 先分配固定內(nèi)存空間來存放方法列表当犯、屬性列表和協(xié)議列表
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];
// entry.cat就是category_t *cat
// 根據(jù)isMeta屬性取出每一個分類的類方法列表或者對象方法列表
method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
// 如果有方法則添加mlist數(shù)組到mlists這個大的方法數(shù)組中
// mlists是一個二維數(shù)組:[[method_t, method_t, ....], [method_t, method_t, ....]]
if (mlist) {
if (mcount == ATTACH_BUFSIZ) {
prepareMethodLists(cls, mlists, mcount, NO, fromBundle, __func__);
rwe->methods.attachLists(mlists, mcount);
mcount = 0;
}
// 將分類列表里先取出來的分類方法列表放到大數(shù)組mlists的最后面(ATTACH_BUFSIZ - ++mcount),所以最后編譯的分類方法列表會放在整個方法列表大數(shù)組的最前面
mlists[ATTACH_BUFSIZ - ++mcount] = mlist;
fromBundle |= entry.hi->isBundle();
}
// 同上面一樣取出的是分類中的屬性列表proplist加到大數(shù)組proplists中
// proplists是一個二維數(shù)組:[[property_t, property_t, ....], [property_t, property_t, ....]]
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;
}
// 同上面一樣取出的是分類中的協(xié)議列表protolist加到大數(shù)組protolists中
// protolists是一個二維數(shù)組:[[protocol_ref_t, protocol_ref_t, ....], [protocol_ref_t, protocol_ref_t, ....]]
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);
if (flags & ATTACH_EXISTING) {
flushCaches(cls, __func__, [](Class c){
return !c->cache.isConstantOptimizedCache();
});
}
}
// 將分類的所有屬性附加到類對象的屬性列表中
rwe->properties.attachLists(proplists + ATTACH_BUFSIZ - propcount, propcount);
// 將分類的所有協(xié)議附加到類對象的協(xié)議列表中
rwe->protocols.attachLists(protolists + ATTACH_BUFSIZ - protocount, protocount);
}
里面有關(guān)鍵性代碼 attachLists
, 在此處進(jìn)行了分類方法的加載順序嚎卫。
void attachLists(List* const * addedLists, uint32_t addedCount) {
if (addedCount == 0) return;
if (hasArray()) {
// 這里是把類的地址重新分配新的地址加 newcount 也是分類的創(chuàng)建個數(shù),而老的分類信息重新復(fù)制一下內(nèi)存宏榕,通過 i-- 倒著取值拓诸,所以最先編譯的會在最后面侵佃,
// 獲取原本的個數(shù)
uint32_t oldCount = array()->count;
// 最新的個數(shù) = 原本的個數(shù) + 新添加的個數(shù)
uint32_t newCount = oldCount + addedCount;
// 重新分配內(nèi)存
array_t *newArray = (array_t *)malloc(array_t::byteSize(newCount));
// 將新數(shù)組的個數(shù)改為最新的個數(shù)
newArray->count = newCount;
// 將舊數(shù)組的個數(shù)改為最新的個數(shù)
array()->count = newCount;
// 遞減遍歷,將舊數(shù)組里的元素從后往前的依次放到新數(shù)組里
for (int i = oldCount - 1; i >= 0; i--)
newArray->lists[i + addedCount] = array()->lists[i];
// 將新增加的元素從前往后的依次放到新數(shù)組里
for (unsigned i = 0; i < addedCount; i++)
newArray->lists[i] = addedLists[i];
// 釋放舊數(shù)組數(shù)據(jù)
free(array());
// 賦值新數(shù)組數(shù)據(jù)
setArray(newArray);
validate();
}
else if (!list && addedCount == 1) {
// 0 lists -> 1 list
list = addedLists[0];
validate();
}
else { .... }
}
上面內(nèi)容就是分類方法的一個合并流程奠支。
參考文檔:
http://www.babyitellyou.com/details?id=6045c2fe4da5fa50e14ff4f3
http://www.reibang.com/p/ed8d3be7e8f4