Category

category是什么?

categoryObjective-C語言的一個(gè)特性奸鸯,一般稱之為“分類”矢棚,或者“類別”。其作用是在不修改類的源碼的基礎(chǔ)上府喳,給類擴(kuò)展一些接口。說的通俗一點(diǎn)蘑拯,就是給已有類添加方法钝满。

當(dāng)需要為一個(gè)類添加新的方法時(shí),在Objective-C中有四種方法可以做到申窘。
1弯蚜、直接在這個(gè)類里添加;但是這樣剃法,對(duì)于一些封裝的類碎捺,會(huì)破壞其封裝性,且對(duì)于一些系統(tǒng)庫(kù)或者第三方動(dòng)態(tài)庫(kù)中的類贷洲,無法直接添加方法收厨。
2、通過繼承在子類中添加方法优构;繼承的開銷太大诵叁,且增加了代碼的復(fù)雜度,同時(shí)也違背了繼承的初衷钦椭,不建議使用拧额。
3碑诉、通過協(xié)議,實(shí)現(xiàn)擴(kuò)展一個(gè)類的方法侥锦;協(xié)議是個(gè)好東西进栽,確實(shí)很實(shí)用,而且還能降低代碼耦合度恭垦,但是實(shí)現(xiàn)起來過于復(fù)雜快毛,代碼量大,且對(duì)于只需要添加一兩個(gè)方法時(shí)署照,就顯得有點(diǎn)小題大做了祸泪。
4、使用category為類添加方法建芙;一般用于比較簡(jiǎn)單的需求没隘,例如僅僅只需要為類添加一兩個(gè)特定功能的方法。

這里禁荸,我們重點(diǎn)討論一下category的優(yōu)缺點(diǎn);
優(yōu)點(diǎn):
1)在不改變一個(gè)類的情況下右蒲,對(duì)一個(gè)已存在的類添加新的方法。
2)可以再?zèng)]有源代碼的情況下赶熟,對(duì)框架中的類進(jìn)行擴(kuò)展瑰妄。
3)當(dāng)一個(gè)類中的代碼量太大時(shí),可以按功能將代碼中的方法放入到不同的category中映砖,減小單個(gè)文件的體積间坐。
缺點(diǎn):
1)類別中的方法的優(yōu)先級(jí)高于類中的方法,所以類別中的方法有可能會(huì)覆蓋類中的方法邑退。(因此在使用category時(shí)竹宋,需要注意命名,不要重復(fù)了)
2)不能直接添加成員變量地技。
3)可以添加屬性蜈七,但是不會(huì)自動(dòng)生成getter和setter方法。

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

category是Objective-C的語言特性莫矗,探討其實(shí)現(xiàn)原理飒硅,需要從runtime源碼下手,下面就借助源碼簡(jiǎn)單分析一下其實(shí)現(xiàn)的原理吧作谚。
先找到category的定義:

#if __OBJC2__
typedef struct category_t *Category;
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);
};

很明顯三娩,Category和Class一樣,其本質(zhì)都是一個(gè)結(jié)構(gòu)體妹懒;這個(gè)結(jié)構(gòu)體中定義了name尽棕、cls、實(shí)例方法彬伦、類方法滔悉、協(xié)議伊诵、屬性等等,所以按理說回官,分類其實(shí)是可以為類添加方法曹宴、屬性、協(xié)議的歉提,但是不能添加實(shí)例變量笛坦,因?yàn)檫@個(gè)結(jié)構(gòu)體中沒有定義用來存放實(shí)力變量的指針變量。

category加載過程

category的加載過程苔巨,相對(duì)來說就比較復(fù)雜版扩。需要從objc的初始化開始說。
來到故事最開始的地方_objc_init

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

前面幾個(gè)函數(shù)的調(diào)用侄泽,其實(shí)都是準(zhǔn)備工作礁芦,最主要的還是:
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
當(dāng)然,我們并不需要去研究dyld的加載過程悼尾,所以我們并不關(guān)注這個(gè)函數(shù)的具體實(shí)現(xiàn)柿扣,我們關(guān)心的,是這個(gè)函數(shù)的第一個(gè)參數(shù)map_images闺魏。他是一個(gè)回調(diào)函數(shù)指針未状,在* objc-runtime-new.mm中可以找到它:

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

這個(gè)函數(shù)比較簡(jiǎn)單,就兩個(gè)函數(shù)的調(diào)用析桥,第一個(gè)lock司草,猜測(cè)應(yīng)該是和線程有關(guān)的,不做深入研究泡仗,主要看第二個(gè)函數(shù)map_images_nolock埋虹。
這個(gè)函數(shù)有點(diǎn)長(zhǎng),但是其實(shí)我們并不需要對(duì)其深入研究沮焕,因?yàn)檫@個(gè)函數(shù)并不是加載category的細(xì)節(jié),我們找到這個(gè)函數(shù)調(diào)用:

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

_read_images函數(shù)也很長(zhǎng)拉宗,里面有對(duì)類峦树、協(xié)議等的加載過程,當(dāng)然category的加載也在旦事,我們只需要找到關(guān)于category的那部分即可:

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

這一段的注釋魁巩,其實(shí)也說的很明白了:首先,根據(jù)category的目標(biāo)類注冊(cè)category姐浮,然后谷遂,如果目標(biāo)類已經(jīng)實(shí)現(xiàn)的話,重新構(gòu)建目標(biāo)類的方法列表卖鲤。
這段代碼看起來也并不復(fù)雜肾扰,其邏輯就是category的實(shí)例方法畴嘶、協(xié)議或者屬性,只要有一個(gè)不為空集晚,即調(diào)用addUnattachedCategoryForClass函數(shù)來注冊(cè)category窗悯,然后判斷category的目標(biāo)類是否實(shí)現(xiàn),如果實(shí)現(xiàn)偷拔,就調(diào)用remethodizeClass重新構(gòu)建目標(biāo)類的方法列表rebuild the class's method lists蒋院。
很顯然,這里有兩個(gè)關(guān)鍵函數(shù):
注冊(cè)函數(shù):addUnattachedCategoryForClass;
重新構(gòu)建方法的函數(shù):remethodizeClass莲绰;
先看看addUnattachedCategoryForClass函數(shù)的實(shí)現(xiàn)欺旧,探究注冊(cè)過程:

static void addUnattachedCategoryForClass(category_t *cat, Class cls, 
                                          header_info *catHeader)
{
    runtimeLock.assertLocked();

    // DO NOT use cat->cls! cls may be cat->cls->isa instead
    NXMapTable *cats = unattachedCategories();
    category_list *list;

    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));
    }
    list->list[list->count++] = (locstamped_category_t){cat, catHeader};
    NXMapInsert(cats, cls, list);
}

這個(gè)函數(shù)可能看起來有點(diǎn)暈乎,但是仔細(xì)研究蛤签,其實(shí)也很簡(jiǎn)單:
首先辞友,通過unattachedCategories ()取出runtime維護(hù)的MapTable -----category_map,這個(gè)MapTable是以TargetClass為key顷啼,category_list結(jié)構(gòu)體對(duì)象為value的表踏枣,而category_list結(jié)構(gòu)體內(nèi),又定義了locstamped_category_t結(jié)構(gòu)體數(shù)組钙蒙,locstamped_category_t結(jié)構(gòu)體中有定義了category_t結(jié)構(gòu)體指針:

struct locstamped_category_t {
    category_t *cat;
    struct header_info *hi;
};

struct locstamped_category_list_t {
    uint32_t count;
#if __LP64__
    uint32_t reserved;
#endif
    locstamped_category_t list[0];
};

typedef locstamped_category_list_t category_list;

這就說明了category_list是通過locstamped_category_t結(jié)構(gòu)體數(shù)組來存儲(chǔ)每一個(gè)category_t對(duì)象的茵瀑;這也解釋了為什么一個(gè)類可以定義多個(gè)category
而這段代碼:

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));
    }
    list->list[list->count++] = (locstamped_category_t){cat, catHeader};
}

則說明了category的注冊(cè)過程:
判斷以TargetClass為key從MapTable中取出的類型為category_listlist指針是否為空躬厌,如果為空马昨,則為list指針申請(qǐng)空間;如果不為空扛施,則在list所占用的空間上鸿捧,追加申請(qǐng)一個(gè)大小為sizeof(list->list[0])的空間,最后疙渣,將catcatHeader存儲(chǔ)到list->list數(shù)組中匙奴。
以上就是category的注冊(cè)過程,接下來看看方法的重新綁定過程:

/***********************************************************************
* remethodizeClass
* Attach outstanding categories to an existing class.
* Fixes up cls's method list, protocol list, and property list.
* Updates method caches for cls and its subclasses.
* Locking: runtimeLock must be held by the caller
**********************************************************************/
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);
    }
}

這個(gè)函數(shù)寥寥數(shù)語妄荔,其實(shí)也很好搞懂泼菌,通過目標(biāo)類名,獲取unattachedcategory_list啦租,并調(diào)用attachCategories函數(shù)哗伯,將category_list綁定到目標(biāo)類上。
這個(gè)綁定過程篷角,涉及到屬性焊刹、協(xié)議以及方法的綁定,這里,我們重點(diǎn)分析category的方法重新綁定過程:

method_list_t **mlists = (method_list_t **)
        malloc(cats->count * sizeof(*mlists));
......
 int mcount = 0;
bool fromBundle = NO;
int i = cats->count;
while (i--) {
        auto& entry = cats->list[i];

        method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
        if (mlist) {
            mlists[mcount++] = mlist;
            fromBundle |= entry.hi->isBundle();
        }
        ......
}
auto rw = cls->data();
prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
rw->methods.attachLists(mlists, mcount);
free(mlists);

這段代碼其實(shí)就做了一件事:將cats->list數(shù)組中的數(shù)據(jù)取出虐块,并存放到mlists數(shù)組中俩滥,
然后將mlists作為參數(shù),傳遞以給attachLists函數(shù)非凌,當(dāng)然举农,同樣作為參數(shù)的還有數(shù)組的count
所以咱們還得繼續(xù)跟進(jìn)到attachLists函數(shù)中:

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

這里粗略的看看三個(gè)if else語句內(nèi)的代碼敞嗡,其實(shí)不難看出颁糟,這三個(gè)地方,都是在做同一個(gè)操作-----將addedLists中的數(shù)據(jù)喉悴,拷貝到array()->lists棱貌。我們第一個(gè)if拿出來分析一下:

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

首先,計(jì)算出新的array()->count:

            uint32_t oldCount = array()->count;
            uint32_t newCount = oldCount + addedCount;

然后箕肃,用新的array()->count婚脱,為array()->lists分配內(nèi)存:

            setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
            array()->count = newCount;

然后,將array()->lists中原來的數(shù)據(jù)勺像,往后移addedCount*sizeof(array()->lists[0])個(gè)字節(jié):

memmove(array()->lists + addedCount, array()->lists, 
                    oldCount * sizeof(array()->lists[0]));

最后障贸,將addedLists中的數(shù)據(jù)復(fù)制到array()->lists的前addedCount*sizeof(array()->lists[0])個(gè)字節(jié)。

至此吟宦,整個(gè)category的分析過程就算完成了篮洁。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市殃姓,隨后出現(xiàn)的幾起案子袁波,更是在濱河造成了極大的恐慌,老刑警劉巖蜗侈,帶你破解...
    沈念sama閱讀 219,490評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件篷牌,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡踏幻,警方通過查閱死者的電腦和手機(jī)枷颊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來该面,“玉大人夭苗,你說我怎么就攤上這事∵壕耄” “怎么了听诸?”我有些...
    開封第一講書人閱讀 165,830評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵坐求,是天一觀的道長(zhǎng)蚕泽。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么须妻? 我笑而不...
    開封第一講書人閱讀 58,957評(píng)論 1 295
  • 正文 為了忘掉前任仔蝌,我火速辦了婚禮,結(jié)果婚禮上荒吏,老公的妹妹穿的比我還像新娘敛惊。我一直安慰自己,他們只是感情好绰更,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,974評(píng)論 6 393
  • 文/花漫 我一把揭開白布瞧挤。 她就那樣靜靜地躺著,像睡著了一般儡湾。 火紅的嫁衣襯著肌膚如雪特恬。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,754評(píng)論 1 307
  • 那天徐钠,我揣著相機(jī)與錄音癌刽,去河邊找鬼。 笑死尝丐,一個(gè)胖子當(dāng)著我的面吹牛显拜,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播爹袁,決...
    沈念sama閱讀 40,464評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼远荠,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了呢簸?” 一聲冷哼從身側(cè)響起矮台,我...
    開封第一講書人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎根时,沒想到半個(gè)月后瘦赫,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,847評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蛤迎,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,995評(píng)論 3 338
  • 正文 我和宋清朗相戀三年确虱,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片替裆。...
    茶點(diǎn)故事閱讀 40,137評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡校辩,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出辆童,到底是詐尸還是另有隱情宜咒,我是刑警寧澤,帶...
    沈念sama閱讀 35,819評(píng)論 5 346
  • 正文 年R本政府宣布把鉴,位于F島的核電站故黑,受9級(jí)特大地震影響儿咱,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜场晶,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,482評(píng)論 3 331
  • 文/蒙蒙 一混埠、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧诗轻,春花似錦钳宪、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,023評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至恨樟,卻和暖如春侦高,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背厌杜。 一陣腳步聲響...
    開封第一講書人閱讀 33,149評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工奉呛, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人夯尽。 一個(gè)月前我還...
    沈念sama閱讀 48,409評(píng)論 3 373
  • 正文 我出身青樓瞧壮,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親匙握。 傳聞我的和親對(duì)象是個(gè)殘疾皇子咆槽,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,086評(píng)論 2 355

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

  • 簡(jiǎn)介 當(dāng)我們功能越來越多時(shí),單一文件的體積就會(huì)變大圈纺,甚至臃腫秦忿。而多人配合開發(fā)一個(gè)文件時(shí),還會(huì)出現(xiàn)不少?zèng)_突蛾娶,這時(shí)就要...
    顯生宙閱讀 2,931評(píng)論 0 3
  • Category和Extension的區(qū)別 1灯谣、Category:類別,分類 類別是一種為現(xiàn)有的類添加新方法的方式...
    荒漠現(xiàn)甘泉閱讀 4,245評(píng)論 0 7
  • 本文主要學(xué)習(xí)Objective-C的runtime源碼時(shí)整理所成蛔琅,主要剖析了category在runtime層的實(shí)...
    michaelJackDong閱讀 547評(píng)論 0 4
  • OC中提供的category特性可以讓我們動(dòng)態(tài)的為現(xiàn)有類添加新的行為胎许,以比繼承更為簡(jiǎn)潔的方法來對(duì)Class進(jìn)行擴(kuò)展...
    三十六_閱讀 650評(píng)論 0 0
  • 預(yù)熱作業(yè)2 :如果你要和30個(gè)小伙伴一起創(chuàng)作一個(gè)好玩的項(xiàng)目,你希望是什么罗售?請(qǐng)列舉至少3項(xiàng)例如:拍視頻辜窑,制作電子書…...
    PP龍青閱讀 272評(píng)論 0 0