平時(shí)我們應(yīng)該會(huì)比較常用分類,今天我們直接分析Category的本質(zhì)原理鸯隅,分析過(guò)后應(yīng)該對(duì)于分類的大部分問(wèn)題都能有一個(gè)自信的答案磅摹。
Category本質(zhì)
我們已經(jīng)OC中類的本質(zhì)都是結(jié)構(gòu)體户誓,分類也不例外幕侠,也是結(jié)構(gòu)體晤硕,直接上源碼結(jié)構(gòu)
struct category_t {
const char *name; //類的名字
classref_t cls; //類
struct method_list_t *instanceMethods; //實(shí)例方法的列表
struct method_list_t *classMethods;//類方法的列表
struct protocol_list_t *protocols; //協(xié)議的列表
struct property_list_t *instanceProperties; //屬性列表
// Fields below this point are not always present on disk.
struct property_list_t *_classProperties;
method_list_t *methodsForMeta(bool isMeta) {
if (isMeta) return classMethods;
else return instanceMethods;
}
property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
};
這里就是Category的真正結(jié)構(gòu),我們會(huì)發(fā)現(xiàn)它只有屬性列表舰褪,沒(méi)有成員變量占拍。那么分類他的實(shí)現(xiàn)原理是什么呢?我們看下它編譯之后的樣子表牢。
先創(chuàng)建這樣一個(gè)分類
//Person+AddCategory.h
@interface Person (AddCategory)<NSCopying>
@property(nonatomic, assign)int age;
- (void)eat;
- (void)run;
+ (void)live;
@end
//Person+AddCategory.m
@implementation Person (AddCategory)
- (void)eat{
NSLog(@"category-eat");
}
- (void)run{
NSLog(@"category-run");
}
+ (void)live{
NSLog(@"category-live");
}
@end
我們通過(guò)clang編譯一下這個(gè).m文件
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc Person+AddCategory.m
在生產(chǎn)的.cpp文件中崔兴,我們可以找到這些代碼
和源碼中看到類似的結(jié)構(gòu)體蛔翅,這里只有主要的方法、協(xié)議折汞、屬性列表等信息
再向下看_method_list_t類型的結(jié)構(gòu)體爽待,下面={}中的結(jié)構(gòu)和上面的定義結(jié)構(gòu)一致翩腐,并且可以在里面看到我們?cè)贑ategory里面實(shí)現(xiàn)的對(duì)象方法茂卦。
還有一個(gè)_method_list_t等龙,這里是類方法
下面是協(xié)議列表:
然后是屬性列表:
后面是對(duì)一個(gè)新的_category_t類型的
_OBJC_$_CATEGORY_Person_$_AddCategory
結(jié)構(gòu)體賦值蛛砰,與_category_t結(jié)構(gòu)體結(jié)構(gòu)一致最后泥畅,編譯器在'__DATA'段下的
__objc_catlist
section里保存了一個(gè)大小為1的category_t的數(shù)組L_OBJC_LABEL_CATEGORY$(如果有多個(gè)category,會(huì)生成對(duì)應(yīng)長(zhǎng)度的數(shù)組)柑贞,用于運(yùn)行期category的加載。后面我們分析Category在運(yùn)行時(shí)的加載棠众。Category加載
為了了解清楚分類在runtime到底是怎么加載的摄欲,我們直接看runtime源碼疮薇,runtime初始化入口
方法調(diào)用順序:進(jìn)入map_images-->map_images_nolock-->_read_images
在_read_images方法里看到注釋有 // Discover categories.
這里代碼太長(zhǎng)按咒,不好截圖(其實(shí)就是懶)
// 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);
}
}
}
}
ts.log("IMAGE TIMES: discover categories");
在這段代碼中可以看到有獲取分類列表catList,然后遍歷里面的分類category_t *cat,然后關(guān)鍵位置智袭,對(duì)象方法remethodizeClass(cls);類方法remethodizeClass(cls->ISA());
都是remethodizeClass()方法掠抬,我們?cè)倏催@里的實(shí)現(xiàn)
再看attachCategories方法
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
//為方法列表两波、屬性列表腰奋、協(xié)議列表分配內(nèi)存
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--) {//注意這里是倒敘遍歷cat
auto& entry = cats->list[i];
//這里是給類中添加分類的對(duì)象方法還是元類中添加分類的類方法,都是走這里嘀倒,我們暫時(shí)認(rèn)為是給類中添加對(duì)象方法
method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
//把分類中的對(duì)象方法列表放到mlists中
if (mlist) {
mlists[mcount++] = mlist;
fromBundle |= entry.hi->isBundle();
}
//把分類中的屬性列表放到proplist中
property_list_t *proplist =
entry.cat->propertiesForMeta(isMeta, entry.hi);
if (proplist) {
proplists[propcount++] = proplist;
}
//把分類中的協(xié)議列表放到protolist中
protocol_list_t *protolist = entry.cat->protocols;
if (protolist) {
protolists[protocount++] = protolist;
}
}
//之前分析類結(jié)構(gòu)的時(shí)候的class_rw_t結(jié)構(gòu)體测蘑。
auto rw = cls->data();
//把mlists附加到class_rw_t中的methods上康二,attachLists(mlists, mcount)方法
prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
rw->methods.attachLists(mlists, mcount);
free(mlists);
if (flush_caches && mcount > 0) flushCaches(cls);
//把proplists附加到class_rw_t中的properties上赠摇,attachLists(proplists, propcount)方法
rw->properties.attachLists(proplists, propcount);
free(proplists);
//把protolists附加到class_rw_t中的protocols上浅蚪,attachLists(protolists, protocount)方法
rw->protocols.attachLists(protolists, protocount);
free(protolists);
}
這里總結(jié):就是獲取分類列表烫罩,然后倒敘遍歷取出分類贝攒,然后將所有分類中的方法列表时甚、屬性列表、協(xié)議列表放到一個(gè)數(shù)組(二維數(shù)組:mlist梨熙、proplists刀诬、protolosts)中陕壹。
然后通過(guò)再通過(guò)對(duì)應(yīng)結(jié)構(gòu)的.attachLists方法附加到原來(lái)的類里的class_rw_t結(jié)構(gòu)體的對(duì)應(yīng)列表中。
這里還有比較關(guān)鍵的一點(diǎn)嘶伟,attachLists方法的實(shí)現(xiàn)又碌,看源碼
void attachLists(List* const * addedLists, uint32_t addedCount) {
if (addedCount == 0) return;
if (hasArray()) {
// many lists -> many lists
uint32_t oldCount = array()->count; //類中原數(shù)組,長(zhǎng)度
uint32_t newCount = oldCount + addedCount; //加上新數(shù)組長(zhǎng)度(分類中的)
setArray((array_t *)realloc(array(), array_t::byteSize(newCount))); //重新分配空間
array()->count = newCount;
//關(guān)鍵,內(nèi)存移動(dòng)耽装,將原數(shù)組array()->lists(內(nèi)存起始位置)這塊內(nèi)存(大小是oldCount * sizeof(array()->lists[0]))移動(dòng)到array()->lists + addedCount這個(gè)位置掉奄。
memmove(array()->lists + addedCount, array()->lists,
oldCount * sizeof(array()->lists[0]));
//關(guān)鍵凤薛,內(nèi)存拷貝,將分類添加的列表數(shù)組addedLists(大小是addedCount * sizeof(array()->lists[0]))copy到array()->lists這個(gè)位置速兔。
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]));
}
}
這里主要的就是
- memmove(array()->lists + addedCount, array()->lists, oldCount * sizeof(array()->lists[0]));
- memcpy(array()->lists, addedLists, addedCount * sizeof(array()->lists[0]));
array()->lists是原列表數(shù)組涣狗,addedLists是分類添加的列表數(shù)組。首先是根據(jù)addedLists擴(kuò)容穗熬,然后把原列表移動(dòng)到列表最后丁溅,然后將addedLists添加到列表開(kāi)頭位置窟赏。
- 這也就是為什么同樣的方法名,分類的方法會(huì)覆蓋原來(lái)類的方法(PS:并不是真正的覆蓋棍掐,只是分類方法在原來(lái)類的方法列表的前面拷况,在查找方法時(shí),先查找到分類的方法就調(diào)用了最疆,原來(lái)類的方法就不會(huì)被調(diào)用了)蚤告。
- 那么問(wèn)題又來(lái)了杜恰,多個(gè)分類分同樣方法先調(diào)用誰(shuí)的?我們注意一下分類在編譯的時(shí)候編譯完成就加入到catList中了舔涎,但是之前在遍歷分類的時(shí)候是倒敘遍歷的逗爹,所以也就是說(shuō)后編譯的分類最后attachCategories中組裝的時(shí)候會(huì)在最前面,這也就可以解答這個(gè)問(wèn)題挟冠。
- 最后袍睡,編譯順序是什么樣的呢?就是我們那在build Phases 中compile Source文件從上到下的順序控淡。
本來(lái)是有面試題的,后來(lái)發(fā)現(xiàn)了解這些后面試題就顯得比較簡(jiǎn)單辫诅,有其他問(wèn)題大家再一起討論吧竹伸。有不對(duì)的請(qǐng)指出簇宽,一起學(xué)習(xí)魏割,共勉!
文章編寫(xiě)參考MJ課程和iOS底層原理總結(jié) - Category的本質(zhì)