開(kāi)篇之前大家先思考這兩個(gè)問(wèn)題
Category的實(shí)現(xiàn)原理烈掠?
Category和Extension的區(qū)別是什么矩父?Class Extension在編譯的時(shí)候致讥,它的數(shù)據(jù)就已經(jīng)包含在類信息中
Category是在運(yùn)行時(shí)岩齿,才會(huì)將數(shù)據(jù)合并到類信息中
開(kāi)始分析Category的源碼
- 1.創(chuàng)建個(gè)LSPerson類
@interface LSPerson : NSObject
@end
@implementation LSPerson
@end
- 2.創(chuàng)建個(gè)LSPerson的Test分類
@interface LSPerson (Test)<NSCopying,NSCoding>
@property (nonatomic,copy)NSString *name1;
@property (nonatomic,assign)int age;
@end
@implementation LSPerson (Test)
-(void)test1
{
NSLog(@"LSPerson (Test1)");
}
-(void)test2
{
NSLog(@"LSPerson (Test2)");
}
+(void)test3
{
NSLog(@"LSPerson (Test3)");
}
-(id)copy
{
return [[LSPerson alloc]init];
}
@end
- 從上面的文件中可以看到這個(gè)分類含有2個(gè)屬性虫碉,2個(gè)方法,遵循了兩個(gè)協(xié)議
- 那么現(xiàn)在我們用clang生成cpp代碼看一下文件里都有啥
- clang命令如下
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc LSPerson+Test.m
struct _category_t {
const char *name; //哪個(gè)類的分類 LSPerson
struct _class_t *cls; //這個(gè)值沒(méi)用到傳的為0
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;//屬性列表
};
- 經(jīng)過(guò)仔細(xì)查看叠荠,看到了cpp文件里有這么個(gè)結(jié)構(gòu)體名字寫(xiě)的也很清楚匿沛,分類的結(jié)構(gòu)體,存放分類的信息榛鼎,那么在接著看在哪用到這個(gè)結(jié)構(gòu)體了,又看到了下面代碼
static struct _category_t _OBJC_$_CATEGORY_LSPerson_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
"LSPerson",
0, // &OBJC_CLASS_$_LSPerson,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_LSPerson_$_Test,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_LSPerson_$_Test,
(const struct _protocol_list_t *)&_OBJC_CATEGORY_PROTOCOLS_$_LSPerson_$_Test,
(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_LSPerson_$_Test,
};
- 看上面這段代碼是定義了一個(gè)_category_t類型的變量逃呼,變量名稱就是
_OBJC_$_CATEGORY_類名_$_分類名
,這種格式變量就不會(huì)重復(fù)者娱,然后進(jìn)行賦值抡笼,有哪幾個(gè)參數(shù)那個(gè)結(jié)構(gòu)體看的也很清楚了,包含類名黄鳍,對(duì)象方法列表推姻,類方法列表,協(xié)議列表际起,屬性列表
類名就是LSPerson也都明白拾碌,那么接著看_OBJC_$_CATEGORY_INSTANCE_METHODS_LSPerson_$_Test
這個(gè)變量吐葱,可以看到是取這個(gè)變量地址然后轉(zhuǎn)成這個(gè)類型(const struct _method_list_t *),然后搜索這個(gè)變量名字,發(fā)現(xiàn)如下代碼
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[2];
} _OBJC_$_CATEGORY_INSTANCE_METHODS_LSPerson_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
2,
{{(struct objc_selector *)"test1", "v16@0:8", (void *)_I_LSPerson_Test_test1},
{(struct objc_selector *)"test2", "v16@0:8", (void *)_I_LSPerson_Test_test2}}
};
可以看出里面含有我們定義的兩個(gè)對(duì)象方法,并且方法count=2
- 我們從上面看到了_objc_method這個(gè)結(jié)構(gòu)體我們?cè)谒阉魉慕Y(jié)構(gòu)街望,發(fā)現(xiàn)就是方法名字校翔,方法類型,方法實(shí)現(xiàn)IMP
struct _objc_method {
struct objc_selector * _cmd;
const char *method_type;
void *_imp;
};
接著看類方法灾前,搜索那個(gè)類方法變量防症,又看到如下代碼
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_LSPerson_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"test3", "v16@0:8", (void *)_C_LSPerson_Test_test3}}
};
可以看出里面含有我們定義的一個(gè)類方法,并且方法count=1
然后在看協(xié)議列表,發(fā)現(xiàn)如下代碼
static struct /*_protocol_list_t*/ {
long protocol_count; // Note, this is 32/64 bit
struct _protocol_t *super_protocols[2];
} _OBJC_CATEGORY_PROTOCOLS_$_LSPerson_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
2,
&_OBJC_PROTOCOL_NSCopying,
&_OBJC_PROTOCOL_NSCoding
};
可以看到包含著我們遵循的兩個(gè)協(xié)議NSCopying,NSCoding哎甲,但是看起來(lái)是兩個(gè)變量蔫敲,再接著看這倆變量是啥
struct _protocol_t _OBJC_PROTOCOL_NSCopying __attribute__ ((used)) = {
0,
"NSCopying",
0,
(const struct method_list_t *)&_OBJC_PROTOCOL_INSTANCE_METHODS_NSCopying,
0,
0,
0,
0,
sizeof(_protocol_t),
0,
(const char **)&_OBJC_PROTOCOL_METHOD_TYPES_NSCopying
};
看到我們傳了協(xié)議名稱,還有方法列表變量炭玫,在看方法列表變量是啥
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[1];
} _OBJC_PROTOCOL_INSTANCE_METHODS_NSCopying __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"copyWithZone:", "@24@0:8^{_NSZone=}16", 0}}
};
由上面看到了這個(gè)方法列表里有我們實(shí)現(xiàn)的copy方法奈嘿,底層調(diào)用的是copyWithZone,和alloc類似調(diào)用的是allocWithZone
那么接下來(lái)在看看協(xié)議的結(jié)構(gòu)體如下吞加,看起來(lái)也是一目了然
struct _protocol_t {
void * isa; // NULL
const char *protocol_name;
const struct _protocol_list_t * protocol_list; // super protocols
const struct method_list_t *instance_methods;
const struct method_list_t *class_methods;
const struct method_list_t *optionalInstanceMethods;
const struct method_list_t *optionalClassMethods;
const struct _prop_list_t * properties;
const unsigned int size; // sizeof(struct _protocol_t)
const unsigned int flags; // = 0
const char ** extendedMethodTypes;
};
接下來(lái)看屬性列表源碼
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_LSPerson_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_prop_t),
2,
{{"name1","T@\"NSString\",C,N"},
{"age","Ti,N"}}
};
確實(shí)看到了我們定義的兩個(gè)name裙犹,age屬性
由此我們得到結(jié)論就是
分類在編譯的時(shí)候?qū)⒎诸惖男畔⒋嬖趕truct _category_t中,那么怎么在程序運(yùn)行的時(shí)候是怎么加載到內(nèi)存中的呢衔憨,接下來(lái)看runtime的源碼
- 我們看到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
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) {
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);
}
由此我們得到以下結(jié)論,Category的加載處理過(guò)程
1.通過(guò)Runtime加載某個(gè)類的所有Category數(shù)據(jù)
2.把所有Category的方法叶圃、屬性、協(xié)議數(shù)據(jù)践图,合并到一個(gè)大數(shù)組中
后面參與編譯的Category數(shù)據(jù)掺冠,會(huì)在數(shù)組的前面
3.將合并后的分類數(shù)據(jù)(方法、屬性码党、協(xié)議)德崭,插入到類原來(lái)數(shù)據(jù)的前面
上圖是Objc類對(duì)象,元類對(duì)象的底層結(jié)構(gòu)揖盘,而我們分類添加的方法是添加到class_rw_t的方法列表里眉厨,從上面分析的代碼可以看到訪問(wèn)的是
auto rw = cls->data();
prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
rw->methods.attachLists(mlists, mcount);
所以calss_ro_t的baseMethodList不會(huì)改變
接下來(lái)驗(yàn)證同時(shí)用分類,和類擴(kuò)展添加屬性扣讼,然后在獲取屬性列表看看順序是啥樣
- 首先在LSPerson類擴(kuò)展里添加兩個(gè)屬性缺猛,分類里的name,age屬性不變
@interface LSPerson : NSObject
@property (nonatomic,copy)NSString *name1;
@property (nonatomic,copy)NSString *name2;
@end
@interface LSPerson()
@property (nonatomic,copy)NSString *extensionPro1;
@property (nonatomic,copy)NSString *extensionPro2;
@end
@implementation LSPerson
@end
@interface LSPerson (Test)<NSCopying,NSCoding>
@property (nonatomic,copy)NSString *categoryName1;
@property (nonatomic,copy)NSString *categoryName2;
@end
@implementation LSPerson (Test)
@end
- 打印屬性列表椭符,使用此庫(kù)比較方便 DLIntrospection
- 可以看到打印一下結(jié)果證明
先是類本身的東西
然后編譯的時(shí)候把類擴(kuò)展的東西插在原來(lái)的前面
編譯的時(shí)候同時(shí)把分類的信息存放在category_t
結(jié)構(gòu)體里
程序運(yùn)行的時(shí)候利用runtime把分類的信息繼續(xù)插在最前面
所以存放順序應(yīng)該是:
分類荔燎,類擴(kuò)展,本類信息
(
"@property (nonatomic, copy) NSString* categoryName1",
"@property (nonatomic, copy) NSString* categoryName2",
"@property (nonatomic, copy) NSString* extensionPro1",
"@property (nonatomic, copy) NSString* extensionPro2",
"@property (nonatomic, copy) NSString* name1",
"@property (nonatomic, copy) NSString* name2"
)