iOS底層原理 - category的本質(zhì)以及源碼分析

開(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的源碼


屏幕快照 2018-11-21 上午11.33.26.png
  • 我們看到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);
}
1542772124422.jpg
E70CD585-A902-4604-AAA1-079FC44C8C1E.png
BEA8A89A-5082-4AE8-A001-C1E122EC2A78.png

由此我們得到以下結(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ù)的前面

屏幕快照 2018-11-21 下午1.35.32.png

上圖是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"
)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末销钝,一起剝皮案震驚了整個(gè)濱河市有咨,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌蒸健,老刑警劉巖座享,帶你破解...
    沈念sama閱讀 216,692評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件婉商,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡渣叛,警方通過(guò)查閱死者的電腦和手機(jī)丈秩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,482評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)淳衙,“玉大人蘑秽,你說(shuō)我怎么就攤上這事◇锱剩” “怎么了肠牲?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,995評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)靴跛。 經(jīng)常有香客問(wèn)我缀雳,道長(zhǎng),這世上最難降的妖魔是什么梢睛? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,223評(píng)論 1 292
  • 正文 為了忘掉前任肥印,我火速辦了婚禮,結(jié)果婚禮上扬绪,老公的妹妹穿的比我還像新娘竖独。我一直安慰自己,他們只是感情好挤牛,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,245評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布莹痢。 她就那樣靜靜地躺著,像睡著了一般墓赴。 火紅的嫁衣襯著肌膚如雪竞膳。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,208評(píng)論 1 299
  • 那天诫硕,我揣著相機(jī)與錄音坦辟,去河邊找鬼。 笑死章办,一個(gè)胖子當(dāng)著我的面吹牛锉走,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播藕届,決...
    沈念sama閱讀 40,091評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼挪蹭,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了休偶?” 一聲冷哼從身側(cè)響起梁厉,我...
    開(kāi)封第一講書(shū)人閱讀 38,929評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎踏兜,沒(méi)想到半個(gè)月后词顾,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體八秃,經(jīng)...
    沈念sama閱讀 45,346評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,570評(píng)論 2 333
  • 正文 我和宋清朗相戀三年肉盹,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了昔驱。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,739評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡垮媒,死狀恐怖舍悯,靈堂內(nèi)的尸體忽然破棺而出航棱,到底是詐尸還是另有隱情睡雇,我是刑警寧澤,帶...
    沈念sama閱讀 35,437評(píng)論 5 344
  • 正文 年R本政府宣布饮醇,位于F島的核電站它抱,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏朴艰。R本人自食惡果不足惜观蓄,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,037評(píng)論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望祠墅。 院中可真熱鬧侮穿,春花似錦、人聲如沸毁嗦。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,677評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)狗准。三九已至克锣,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間腔长,已是汗流浹背袭祟。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,833評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留捞附,地道東北人巾乳。 一個(gè)月前我還...
    沈念sama閱讀 47,760評(píng)論 2 369
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像鸟召,于是被迫代替她去往敵國(guó)和親胆绊。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,647評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • iOS底層原理總結(jié) - Category的本質(zhì) 面試題 Category的實(shí)現(xiàn)原理药版,以及Category為什么只能...
    xx_cc閱讀 30,452評(píng)論 36 199
  • 1.ios高性能編程 (1).內(nèi)層 最小的內(nèi)層平均值和峰值(2).耗電量 高效的算法和數(shù)據(jù)結(jié)構(gòu)(3).初始化時(shí)...
    歐辰_OSR閱讀 29,373評(píng)論 8 265
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒(méi)有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對(duì)...
    cosWriter閱讀 11,097評(píng)論 1 32
  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉辑舷,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,709評(píng)論 0 9
  • 剛開(kāi)始玩這款寫(xiě)作軟件,來(lái)個(gè)大哥哥叫我寫(xiě)作啊槽片,萌新昂位骸肢础!求帶啊碌廓!
    慕光閃閃閱讀 171評(píng)論 0 0