詳解iOS中分類Cateogry

為了表示我對(duì)簡(jiǎn)書『飽醉豚』事件的不滿便斥,簡(jiǎn)書不再更新,后續(xù)有文章只更新 個(gè)人博客掘金

歡迎移步 個(gè)人博客
或者 掘金

分類的基本使用

  • 首先我們定義一個(gè)類 YZPerson 繼承自 NSObject
@interface YZPerson : NSObject
@end
  • 然后定義一個(gè)分類 YZPerson+test1.h
#import "YZPerson.h"

@interface YZPerson (test1)
-(void)run;
@end


#import "YZPerson+test1.h"

@implementation YZPerson (test1)
-(void)run{
    NSLog(@"%s",__func__);
}
@end
  • 在控制器 ViewController 中使用
- (void)viewDidLoad {
    [super viewDidLoad];
    
    YZPerson *person = [[YZPerson alloc] init];
    [person run];
}
  • 執(zhí)行結(jié)果為

CateogryDemo[23773:321096] -[YZPerson(test1) run]

注意點(diǎn):如果原來(lái)的類和分類中有同樣的方法秽浇,那么執(zhí)行的結(jié)果的是分類中的谦炒,例如

#import <Foundation/Foundation.h>

@interface YZPerson : NSObject
-(void)run;
@end



#import "YZPerson.h"

@implementation YZPerson
-(void)run{
    NSLog(@"%s",__func__);
}
@end
  • 執(zhí)行結(jié)果不會(huì)發(fā)生變化鸠蚪,依然是

CateogryDemo[23773:321096] -[YZPerson(test1) run]

原因在后面分析

分類的結(jié)構(gòu)

打開終端肠阱,進(jìn)入項(xiàng)目下,執(zhí)行如下命令街佑,生成C語(yǔ)言的文件

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc YZPerson+test1.m

生成 YZPerson+test1.cpp文件

摘取主要代碼如下


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;
};

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_YZPerson_$_test1 __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    1,
    {{(struct objc_selector *)"run", "v16@0:8", (void *)_I_YZPerson_test1_run}}
};


extern "C" __declspec(dllimport) struct _class_t OBJC_CLASS_$_YZPerson;

static struct _category_t _OBJC_$_CATEGORY_YZPerson_$_test1 __attribute__ ((used, section ("__DATA,__objc_const"))) = 
{
    "YZPerson",
    0, // &OBJC_CLASS_$_YZPerson,
    (const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_YZPerson_$_test1,
    0,
    0,
    0,
};


static void OBJC_CATEGORY_SETUP_$_YZPerson_$_test1(void ) {
    _OBJC_$_CATEGORY_YZPerson_$_test1.cls = &OBJC_CLASS_$_YZPerson;
}

說(shuō)明編譯完之后每一個(gè)分類都會(huì)生成一個(gè)

_category_t

的結(jié)構(gòu)體谢翎,里面有名稱,對(duì)象方法列表沐旨,類方法列表森逮,協(xié)議方法列表,屬性列表,如果對(duì)應(yīng)的為空磁携,比如協(xié)議為空褒侧,屬性為空,那么結(jié)構(gòu)體中保存的就是0。

objc-runtime-new.h

打開源碼最新的源碼 runtime源碼看闷供,objc-runtime-new.h中分類結(jié)構(gòu)體是這樣的

struct category_t {
    const char *name;
    classref_t cls;
    struct method_list_t *instanceMethods;
    struct method_list_t *classMethods;
    struct protocol_list_t *protocols;
    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);
};

源碼分析

源碼解讀順序

objc-os.mm
_objc_init
map_images
map_images_nolock

objc-runtime-new.mm
_read_images
remethodizeClass
attachCategories
attachLists
realloc烟央、memmove、 memcpy
  • 先找到 objc-os.mm 類这吻,里面的

// runtime初始化方法
void _objc_init(void) //
{
    static bool initialized = false;
    if (initialized) return;
    initialized = true;
    
    // fixme defer initialization until an objc-using image is found?
    environ_init();
    tls_init();
    static_init();
    lock_init();
    exception_init();

    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
}
  • 繼續(xù)跟下去
map_images(unsigned count, const char * const paths[],
           const struct mach_header * const mhdrs[])
{
    mutex_locker_t lock(runtimeLock);
    return map_images_nolock(count, paths, mhdrs);
}

  • 查看
map_images_nolock

找到

if (hCount > 0) {
        _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
    }

到了文件 objc-runtime-new.mm 中

void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
    // 關(guān)鍵代碼
     remethodizeClass(cls);
     // 關(guān)鍵代碼
     remethodizeClass(cls->ISA());

}

static void remethodizeClass(Class cls)
{
    category_list *cats;
    bool isMeta;

    runtimeLock.assertLocked();

    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);
    }
}

主要代碼合注釋已經(jīng)在代碼中展示了

static void 
attachCategories(Class cls, category_list *cats, bool flush_caches)
{
    // cats 分類列表
    if (!cats) return;
    if (PrintReplacedMethods) printReplacements(cls, cats);
    
    bool isMeta = cls->isMetaClass();
    // 方法數(shù)組 二維數(shù)組
    // fixme rearrange to remove these intermediate allocations
    method_list_t **mlists = (method_list_t **)
    malloc(cats->count * sizeof(*mlists));
    // 屬性數(shù)組 二維數(shù)組
    property_list_t **proplists = (property_list_t **)
    malloc(cats->count * sizeof(*proplists));
    // 協(xié)議數(shù)組 二維數(shù)組
    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;
    // 這個(gè)while循環(huán) 合并分類中的 對(duì)象方法 屬性 協(xié)議
    while (i--) {
        // 取出某個(gè)分類
        auto& entry = cats->list[i];
        // 取出分類中的對(duì)象方法
        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;
        }
        // 取出分類中的協(xié)議
        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);
    // 所有分類的屬性篙议,附加到類對(duì)象的屬性列表中唾糯,
    rw->properties.attachLists(proplists, propcount);
    free(proplists);
    // 所有分類的協(xié)議,附加到類對(duì)象的協(xié)議列表中
    rw->protocols.attachLists(protolists, protocount);
    free(protolists);
}

memmove memcpy

上面的代碼繼續(xù)跟下去來(lái)到了

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;
            // 內(nèi)存挪動(dòng)
            memmove(array()->lists + addedCount, 
                            array()->lists,
                    oldCount * sizeof(array()->lists[0]));
            // 內(nèi)存拷貝
            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]));
        }
    }

其中關(guān)鍵代碼是

// 內(nèi)存挪動(dòng)
 memmove(array()->lists + addedCount,
                    array()->lists,
                    oldCount * sizeof(array()->lists[0]));
// 內(nèi)存拷貝
 memcpy(array()->lists, addedLists,
                   addedCount * sizeof(array()->lists[0]));

關(guān)于memcpy與memmove的區(qū)別鬼贱,可以參考 memcpy與memmove的區(qū)別

簡(jiǎn)單總結(jié)就是:

區(qū)別就在于關(guān)鍵字restrict, memcpy假定兩塊內(nèi)存區(qū)域沒有數(shù)據(jù)重疊移怯,而memmove沒有這個(gè)前提條件。如果復(fù)制的兩個(gè)區(qū)域存在重疊時(shí)使用memcpy这难,其結(jié)果是不可預(yù)知的舟误,有可能成功也有可能失敗的,所以如果使用了memcpy,程序員自身必須確保兩塊內(nèi)存沒有重疊部分

總結(jié)

合并分類的時(shí)候姻乓,其方法列表等嵌溢,不會(huì)覆蓋掉原來(lái)類中的方法,是共存的蹋岩。但是分類中的方法在前面赖草,原來(lái)的類中的方法在后面,調(diào)用的時(shí)候剪个,就會(huì)調(diào)用分類中的方法秧骑,如果多個(gè)分類有同樣的方法,后編譯的分類會(huì)調(diào)用扣囊。

問題

Category的使用場(chǎng)合是什么乎折?

  • 不同模塊的功能區(qū)分開來(lái),可以使用分類實(shí)現(xiàn)

Category的實(shí)現(xiàn)原理

  • Category編譯之后的底層結(jié)構(gòu)是struct category_t侵歇,里面存儲(chǔ)著分類的對(duì)象方法骂澄、類方法、屬性惕虑、協(xié)議信息
    在程序運(yùn)行的時(shí)候坟冲,runtime會(huì)將Category的數(shù)據(jù),合并到類信息中(類對(duì)象枷遂、元類對(duì)象中)

Category和Class Extension的區(qū)別是什么樱衷?

Class Extension在編譯的時(shí)候,它的數(shù)據(jù)就已經(jīng)包含在類信息中
Category是在運(yùn)行時(shí)酒唉,才會(huì)將數(shù)據(jù)合并到類信息中

Category中有l(wèi)oad方法嗎矩桂?load方法是什么時(shí)候調(diào)用的?load 方法能繼承嗎?

有l(wèi)oad方法
load方法在runtime加載類侄榴、分類的時(shí)候調(diào)用
load方法可以繼承雹锣,但是一般情況下不會(huì)主動(dòng)去調(diào)用load方法,都是讓系統(tǒng)自動(dòng)調(diào)用

Category能否添加成員變量癞蚕?如果可以蕊爵,如何給Category添加成員變量?

  • 不能直接給Category添加成員變量桦山,但是可以間接實(shí)現(xiàn)Category有成員變量的效果,關(guān)聯(lián)對(duì)象

本文相關(guān)代碼github地址 github

本文參考資料:

runtime源碼

iOS底層原理

memcpy與memmove的區(qū)別

更多資料攒射,歡迎關(guān)注個(gè)人公眾號(hào),不定時(shí)分享各種技術(shù)文章恒水。

image
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末会放,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子钉凌,更是在濱河造成了極大的恐慌咧最,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件御雕,死亡現(xiàn)場(chǎng)離奇詭異矢沿,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)酸纲,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門捣鲸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人福青,你說(shuō)我怎么就攤上這事摄狱。” “怎么了无午?”我有些...
    開封第一講書人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵媒役,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我宪迟,道長(zhǎng)酣衷,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任次泽,我火速辦了婚禮穿仪,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘意荤。我一直安慰自己啊片,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開白布玖像。 她就那樣靜靜地躺著紫谷,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上笤昨,一...
    開封第一講書人閱讀 51,125評(píng)論 1 297
  • 那天祖驱,我揣著相機(jī)與錄音,去河邊找鬼瞒窒。 笑死捺僻,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的崇裁。 我是一名探鬼主播匕坯,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼寇壳!你這毒婦竟也來(lái)了醒颖?” 一聲冷哼從身側(cè)響起妻怎,我...
    開封第一講書人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤壳炎,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后逼侦,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體匿辩,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年榛丢,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了铲球。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡晰赞,死狀恐怖稼病,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情掖鱼,我是刑警寧澤然走,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站戏挡,受9級(jí)特大地震影響芍瑞,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜褐墅,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一拆檬、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧妥凳,春花似錦竟贯、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春齐莲,著一層夾襖步出監(jiān)牢的瞬間痢站,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工选酗, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留阵难,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓芒填,卻偏偏與公主長(zhǎng)得像呜叫,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子殿衰,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353

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