1.category 是什么职员?
首先拒贱,新建一個(gè)NcFood類瘾境,并添加兩個(gè)分類
#import <Foundation/Foundation.h>
@interface NcFood : NSObject
- (void)color;
@end
@interface NcFood (Apple)
@property(nonatomic,strong)NSString *name;
@end
@interface NcFood (Mango)
@property(nonatomic,strong)NSString *name;
@property(nonatomic,assign)int amount;
@end
#import "NcFood.h"
@implementation NcFood
- (void)color {
NSLog(@"color");
}
@end
@implementation NcFood (Apple)
- (void)color {
NSLog(@"pink");
}
@end
@implementation NcFood (Mango)
- (void)color {
NSLog(@"golden yellow");
}
@end
用Clang編譯成c++文件
終端輸入:
clang -rewrite-objc Ncfood.m
忽略不用的信息铐刘,先查看編譯后的category的結(jié)構(gòu)
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;
};
再看編譯后的分類Apple與Mango
Apple:
// 實(shí)例方法列表
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_NcFood_$_Apple __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"color", "v16@0:8", (void *)_I_NcFood_Apple_color}}
};
// 屬性列表
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_NcFood_$_Apple __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_prop_t),
1,
{{"name","T@\"NSString\",&,N"}}
};
// 分類結(jié)構(gòu)體
static struct _category_t _OBJC_$_CATEGORY_NcFood_$_Apple __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
"NcFood",
0, // &OBJC_CLASS_$_NcFood,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_NcFood_$_Apple,
0,
0,
(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_NcFood_$_Apple,
};
// 此處將_NcFood類的地址賦給Apple分類結(jié)構(gòu)體中的cls
static void OBJC_CATEGORY_SETUP_$_NcFood_$_Apple(void ) {
_OBJC_$_CATEGORY_NcFood_$_Apple.cls = &OBJC_CLASS_$_NcFood;
}
Mango:
// 實(shí)例方法列表
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_NcFood_$_Mango __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"color", "v16@0:8", (void *)_I_NcFood_Mango_color}}
};
// 屬性列表
static struct /*_prop_list_t*/ {
unsigned int entsize; // sizeof(struct _prop_t)
unsigned int count_of_properties;
struct _prop_t prop_list[2];
} _OBJC_$_PROP_LIST_NcFood_$_Mango __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_prop_t),
2,
{{"name","T@\"NSString\",&,N"},
{"amount","Ti,N"}}
};
// 分類結(jié)構(gòu)體
static struct _category_t _OBJC_$_CATEGORY_NcFood_$_Mango __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
"NcFood",
0, // &OBJC_CLASS_$_NcFood,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_NcFood_$_Mango,
0,
0,
(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_NcFood_$_Mango,
};
// 此處將_NcFood類的地址賦給Mango分類結(jié)構(gòu)體中的cls
static void OBJC_CATEGORY_SETUP_$_NcFood_$_Mango(void ) {
_OBJC_$_CATEGORY_NcFood_$_Mango.cls = &OBJC_CLASS_$_NcFood;
}
另外還有一點(diǎn)需要關(guān)注的
static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [2] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= {
&_OBJC_$_CATEGORY_NcFood_$_Apple,
&_OBJC_$_CATEGORY_NcFood_$_Mango,
};
所有的分類都會被保存在這個(gè)表里陪每,在程序啟動運(yùn)行的時(shí)候會遍歷這個(gè)列表,將其中的category與它所屬的類關(guān)聯(lián)镰吵。
編譯后的category就是這個(gè)樣子了
關(guān)于category的屬性
以上兩個(gè)分類(Apple與Mango)都有自己的屬性檩禾,但在實(shí)例方法列表中并沒有看到相應(yīng)的set與get方法,分類的結(jié)構(gòu)體中也沒有用來存儲成員變量的地方疤祭,所以分類中的屬性沒有實(shí)現(xiàn)set與get方法盼产,也沒有自動生成成員變量。
用點(diǎn)語法訪問分類的屬性會導(dǎo)致程序崩潰画株。(點(diǎn)語法本質(zhì)就是調(diào)用set辆飘、get方法)
NcFood *food = [[NcFood alloc] init];
NSLog(@"---%@",food.name); // 此處崩潰
2018-11-20 17:10:45.419760+0800 Nunca[39382:6373286] -[NcFood name]: unrecognized selector sent to instance 0x600000008860
2. category的加載
category的加載發(fā)生在程序啟動后調(diào)用的map_images函數(shù)中(objc-runtime-new.mm類中)
在map_images里面啦辐,在加載category之前還會先加載好Class谓传、_Protocol。
以下是其中加載分類的代碼
// Discover categories.
for (EACH_HEADER) {
//取出編譯時(shí)生成的一個(gè)包含程序中所有category的list
category_t **catlist =
_getObjc2CategoryList(hi, &count);
bool hasClassProperties = hi->info()->hasCategoryClassProperties();
for (i = 0; i < count; i++) { // 遍歷每一個(gè)category
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)
{
// 在此方法中將分類存到類對應(yīng)的其分類的list(類與類的分類可能是一對多的)
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);
}
}
}
}
主要需要關(guān)注兩個(gè)方法
addUnattachedCategoryForClass :將新遍歷到的category添加至還未與類進(jìn)行關(guān)聯(lián)的對應(yīng)的category_list
static void addUnattachedCategoryForClass(category_t *cat, Class cls,
header_info *catHeader)
{
runtimeLock.assertWriting();
// DO NOT use cat->cls! cls may be cat->cls->isa instead
//cats是一個(gè)以cls為key芹关、以category_list為value的map
NXMapTable *cats = unattachedCategories();
category_list *list;
// 根據(jù)cls獲取還未進(jìn)行關(guān)聯(lián)的分類列表
list = (category_list *)NXMapGet(cats, cls);
if (!list) {
list = (category_list *)
calloc(sizeof(*list) + sizeof(list->list[0]), 1);
} else {
list = (category_list *)
realloc(list, sizeof(*list) + sizeof(list->list[0]) * (list->count + 1));
}
// 往未進(jìn)行關(guān)聯(lián)的分類列表中添加新的分類
list->list[list->count++] = (locstamped_category_t){cat, catHeader};
NXMapInsert(cats, cls, list);
}
remethodizeClass :將分類中的各種方法整合至類的方法列表
static void remethodizeClass(Class cls)
{
category_list *cats;
bool isMeta;
runtimeLock.assertWriting();
isMeta = cls->isMetaClass();
// Re-methodizing: check for more categories
// unattachedCategoriesForClass里面會返回類對應(yīng)的分類列表续挟,并將其從map中移除
if ((cats = unattachedCategoriesForClass(cls, false/*not realizing*/))) {
if (PrintConnecting) {
_objc_inform("CLASS: attaching categories to class '%s' %s",
cls->nameForLogging(), isMeta ? "(meta)" : "");
}
// 此處才是真正進(jìn)行關(guān)聯(lián)
attachCategories(cls, cats, true /*flush caches*/);
free(cats);
}
}
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_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--) {
// 倒序遍歷,編譯越后的category侥衬,在list中越靠前
auto& entry = cats->list[i];
method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
if (mlist) {
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;
}
}
auto rw = cls->data();
prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
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,下面摘錄attachLists方法中關(guān)鍵的一段
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]));
}
}
關(guān)于分類的調(diào)用順序
可以看到轴总,新添加的分類的方法會在類的方法的最前部直颅。
越后編譯的分類的方法在類的方法列表中位置越靠前,因此怀樟,如果一個(gè)類與它的分類中存在相同的方法功偿,在調(diào)用此方法時(shí),會從它的類的方法列表去查找往堡,最后被編譯的在列表的最前面械荷,因此被調(diào)用的也是最后被編譯的那個(gè)分類中的那個(gè)方法(即使此分類的頭文件未被導(dǎo)入至方法調(diào)用的類)共耍。
(編譯的順序往往是我們不太關(guān)心確定的,因此最好不要在同一個(gè)類的多個(gè)分類中寫同名的方法)
主要參考:
深入理解Objective-C:Category