- 一盗似、Category 淺層分析
- 二俺亮、Category 底層結(jié)構(gòu)
- 三、Category 源碼分析(分類方法優(yōu)先調(diào)用)
- 四罩润、小結(jié)
一玖翅、Category 淺層分析
參照上圖,思考關(guān)于分類的問題。分類有沒有可能同上圖的
class
或 meta-class
處于并列關(guān)系烧栋,每創(chuàng)建一個(gè)分類分別對(duì)應(yīng)著一個(gè) 分類class
和 分類meta-class
對(duì)象写妥?
這里先說出結(jié)論,在第二小節(jié)再做驗(yàn)證审姓。實(shí)際情況并非如此珍特,為了充分利用資源,一個(gè)類永遠(yuǎn)只存在一個(gè)類對(duì)象魔吐。Category
中的對(duì)象方法會(huì)合并到類(class)
的對(duì)象方法列表
中扎筒,類方法合并到元類(meta-class)
的類方法列表
中。方法合并的時(shí)機(jī)在運(yùn)行時(shí)進(jìn)行酬姆,而非編譯期間嗜桌。
二、Category 底層結(jié)構(gòu)
@implementation Person
- (void)run{
NSLog(@"run");
}
@end
@implementation Person (Test)
- (void)test{
NSLog(@"test");
}
+ (void)test{
}
@end
@implementation Person (Eat)
- (void)eat{
NSLog(@"eat");
}
+ (void)eat{
}
@end
為驗(yàn)證上述問題辞色,可以編寫如上代碼骨宠,即創(chuàng)建 Person
以及 Person + Test
和 Person + Eat
分類。然后借助xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc OC源文件 -o 輸出的CPP文件
命令將 Person + Test
類轉(zhuǎn)為 C/C++ 代碼相满。
生成的 .cpp
文件中包含 _category_t
結(jié)構(gòu)體层亿,該結(jié)構(gòu)體即為分類底層數(shù)據(jù)結(jié)構(gòu),主要包含類名立美、對(duì)象方法匿又、類方法、協(xié)議以及屬性建蹄。
struct _category_t {
const char *name;//類名碌更,這里是Person
struct _class_t *cls;
const struct _method_list_t *instance_methods;//對(duì)象方法列表
const struct _method_list_t *class_methods;//類方法列表
const struct _protocol_list_t *protocols;//協(xié)議列表
const struct _prop_list_t *properties;//屬性列表
};
生成的.cpp
文件中還存在下下面一段代碼,該段代碼與 _category_t
結(jié)構(gòu)體對(duì)應(yīng)洞慎,依次給_category_t
結(jié)構(gòu)體內(nèi)部成員賦值痛单。其中第二、五劲腿、六三個(gè)參數(shù)均為 0桦他。
static struct _category_t _OBJC_$_CATEGORY_Person_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
"Person",
0, // &OBJC_CLASS_$_Person,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_Test,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_Person_$_Test,
0,
0,
};
從上述驗(yàn)證可以看出,編譯期間過后谆棱,每個(gè)分類唯一對(duì)應(yīng)一個(gè) _category_t
結(jié)構(gòu)快压,與原有類是分開的。
三垃瞧、Category 源碼分析(分類方法優(yōu)先調(diào)用)
為進(jìn)一步理解 Category
蔫劣,可以查看在該網(wǎng)站 查看 objc4-723 runtime 底層源碼 「龃樱可以順著如下順序閱讀源碼脉幢,其中 objc-os.mm
文件為運(yùn)行時(shí)的入口文件歪沃。
objc-os.mm ---> _objc_init ---> map_images ---> map_images_nolock --->objc-runtime-new.mm --->_read_images --->remethodizeClass --->attachCategories --->attachLists
在_read_images
方法中可以發(fā)現(xiàn)這樣一段代碼,代碼上方注釋為 Discover categories
, 另外還有個(gè)二維數(shù)組category_t ** catlist
嫌松, catlist
數(shù)組中元素為結(jié)構(gòu)體沪曙,存儲(chǔ)著一堆 category_t
結(jié)構(gòu)。remethodizeClass
方法被調(diào)用兩次萎羔,從命名來看意思為:重新方法化液走,兩次傳入?yún)?shù)分別為 cls
和 cls->ISA
, 即類對(duì)象的元類對(duì)象。
// Discover categories.
for (EACH_HEADER) {
category_t **catlist =
_getObjc2CategoryList(hi, &count);
bool hasClassProperties = hi->info()->hasCategoryClassProperties();
for (i = 0; i < count; i++) {
category_t *cat = catlist[I];
Class cls = remapClass(cat->cls);
if (!cls) {
// Category's target class is missing (probably weak-linked).
// Disavow any knowledge of this category.
catlist[i] = nil;
if (PrintConnecting) {
_objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with "
"missing weak-linked target class",
cat->name, cat);
}
continue;
}
// Process this category.
// First, register the category with its target class.
// Then, rebuild the class's method lists (etc) if
// the class is realized.
bool classExists = NO;
if (cat->instanceMethods || cat->protocols
|| cat->instanceProperties)
{
addUnattachedCategoryForClass(cat, cls, hi);
if (cls->isRealized()) {
remethodizeClass(cls);
classExists = YES;
}
if (PrintConnecting) {
_objc_inform("CLASS: found category -%s(%s) %s",
cls->nameForLogging(), cat->name,
classExists ? "on existing class" : "");
}
}
if (cat->classMethods || cat->protocols
|| (hasClassProperties && cat->_classProperties))
{
addUnattachedCategoryForClass(cat, cls->ISA(), hi);
if (cls->ISA()->isRealized()) {
remethodizeClass(cls->ISA());
}
if (PrintConnecting) {
_objc_inform("CLASS: found category +%s(%s)",
cls->nameForLogging(), cat->name);
}
}
}
}
remethodizeClass
方法內(nèi)部調(diào)用了 attachCategories
方法贾陷,attachCategories
方法前兩個(gè)參數(shù)分別為 class
和 分類數(shù)組cats
缘眶。
static void remethodizeClass(Class cls)
{
category_list *cats;
bool isMeta;
runtimeLock.assertWriting();
isMeta = cls->isMetaClass();
// Re-methodizing: check for more categories
if ((cats = unattachedCategoriesForClass(cls, false/*not realizing*/))) {
if (PrintConnecting) {
_objc_inform("CLASS: attaching categories to class '%s' %s",
cls->nameForLogging(), isMeta ? "(meta)" : "");
}
attachCategories(cls, cats, true /*flush caches*/);
free(cats);
}
}
attachCategories
方法內(nèi)部創(chuàng)建了三個(gè)二維數(shù)組method_list_t **mlists
,property_list_t **proplists
,protocol_list_t **protolists
, 分別用于保存方法、屬性和協(xié)議髓废。mlists[mcount++] = mlist;
表示取出每個(gè)分類中的方法列表放入到二維數(shù)組中巷懈;auto rw = cls->data();
表示取出類對(duì)象中的數(shù)據(jù);rw->methods.attachLists(mlists, mcount);
表示將所有分類的對(duì)象方法附加到類對(duì)象方法列表中慌洪。
static void
attachCategories(Class cls, category_list *cats, bool flush_caches)
{
if (!cats) return;
if (PrintReplacedMethods) printReplacements(cls, cats);
bool isMeta = cls->isMetaClass();
// fixme rearrange to remove these intermediate allocations
//方法數(shù)組 [[method_t, method_t],[method_t, method_t]]
method_list_t **mlists = (method_list_t **)
malloc(cats->count * sizeof(*mlists));
property_list_t **proplists = (property_list_t **)
malloc(cats->count * sizeof(*proplists));
protocol_list_t **protolists = (protocol_list_t **)
malloc(cats->count * sizeof(*protolists));
// Count backwards through cats to get newest categories first
int mcount = 0;
int propcount = 0;
int protocount = 0;
int i = cats->count;
bool fromBundle = NO;
while (i--) {
auto& entry = cats->list[I];
method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
if (mlist) {
//取出分類中的方法列表放入到二維數(shù)組中
mlists[mcount++] = mlist;
fromBundle |= entry.hi->isBundle();
}
property_list_t *proplist =
entry.cat->propertiesForMeta(isMeta, entry.hi);
if (proplist) {
proplists[propcount++] = proplist;
}
protocol_list_t *protolist = entry.cat->protocols;
if (protolist) {
protolists[protocount++] = protolist;
}
}
//取出類對(duì)象中的數(shù)據(jù)
auto rw = cls->data();
prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
//將所有分類的對(duì)象方法附加到類對(duì)象方法列表中
rw->methods.attachLists(mlists, mcount);
free(mlists);
if (flush_caches && mcount > 0) flushCaches(cls);
rw->properties.attachLists(proplists, propcount);
free(proplists);
rw->protocols.attachLists(protolists, protocount);
free(protolists);
}
attachLists
方法中調(diào)用memmove
方法將類中的原有方法方法放到數(shù)組末尾顶燕,調(diào)用memcpy
方法將二維數(shù)組 addedLists
中的每個(gè) Category
的方法列表放置到類中原有方法的前面。
void attachLists(List* const * addedLists, uint32_t addedCount) {
if (addedCount == 0) return;
if (hasArray()) {
// 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]));
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
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;
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
}
四冈爹、小結(jié)
經(jīng)過上述源碼分析涌攻,最終方法在內(nèi)存中的布局結(jié)構(gòu)如下。該布局很好說明了分類方法調(diào)用順序要優(yōu)于原有類方法犯助。因?yàn)檎{(diào)用方法時(shí),會(huì)順序遍歷二維數(shù)組查找方法维咸,當(dāng)查找到目標(biāo)方法后就無需再向后繼續(xù)遍歷查找方法剂买。
補(bǔ)充
1、Category的實(shí)現(xiàn)原理
- Category編譯之后的底層結(jié)構(gòu)是struct category_t癌蓖,里面存儲(chǔ)著分類的對(duì)象方法瞬哼、類方法、屬性租副、協(xié)議信息等
- 在程序運(yùn)行的時(shí)候坐慰,runtime 會(huì)將 Category 的數(shù)據(jù),合并到類信息中(類對(duì)象用僧、元類對(duì)象中)
2结胀、Category 和 Class Extension 的區(qū)別?(注意這里的 extension 指的不是 swift 中的 extension)
- Class Extension 在編譯的時(shí)候责循,它的數(shù)據(jù)就已經(jīng)包含在類信息中糟港。
- Category 是在運(yùn)行時(shí),才會(huì)將數(shù)據(jù)合并到類信息中院仿。
- Extension 可以添加成員變量秸抚,Category 只能添加屬性速和,不能添加成員變量。
3剥汤、分類為什么不能加成員變量颠放?
Category 在代碼里添加成員變量是根本編譯不過去的,而添加屬性是可以編譯通過的吭敢。
這要從Category 的原理說起碰凶,應(yīng)用啟動(dòng)時(shí)通過runtime動(dòng)態(tài)地把Category中的方法添加到類中,蘋果在實(shí)現(xiàn)的過程中并未將屬性添加到類中省有,屬性僅僅是聲明了setter和getter方法痒留,而并未實(shí)現(xiàn)。此時(shí)各個(gè)類的內(nèi)存布局已經(jīng)確定了蠢沿,不可以再更改伸头。
Category 可以加屬性,但是沒有對(duì)應(yīng)的成員變量存儲(chǔ)區(qū)域舷蟀。類的成員變量存儲(chǔ)區(qū)在編譯時(shí)就確定了恤磷。所以只能定義成@dynamic,運(yùn)行時(shí)關(guān)聯(lián)一塊內(nèi)存到對(duì)象野宜。在runtime中存在一個(gè)類型為AssociationHashMap的哈希映射表保存著對(duì)象動(dòng)態(tài)添加的屬性扫步,每個(gè)對(duì)象以自身地址為 key 維護(hù)著一個(gè)綁定屬性表,我們動(dòng)態(tài)添加的屬性就都存儲(chǔ)在這個(gè)表里匈子,這也是動(dòng)態(tài)添加property能成功的基礎(chǔ)河胎。