Category

category是什么?

categoryObjective-C語言的一個特性,一般稱之為“分類”,或者“類別”。其作用是在不修改類的源碼的基礎(chǔ)上,給類擴展一些接口。說的通俗一點,就是給已有類添加方法。

當(dāng)需要為一個類添加新的方法時,在Objective-C中有四種方法可以做到。
1、直接在這個類里添加;但是這樣,對于一些封裝的類,會破壞其封裝性,且對于一些系統(tǒng)庫或者第三方動態(tài)庫中的類,無法直接添加方法。
2、通過繼承在子類中添加方法;繼承的開銷太大,且增加了代碼的復(fù)雜度,同時也違背了繼承的初衷,不建議使用。
3、通過協(xié)議,實現(xiàn)擴展一個類的方法;協(xié)議是個好東西,確實很實用,而且還能降低代碼耦合度,但是實現(xiàn)起來過于復(fù)雜,代碼量大,且對于只需要添加一兩個方法時,就顯得有點小題大做了。
4、使用category為類添加方法;一般用于比較簡單的需求,例如僅僅只需要為類添加一兩個特定功能的方法。

這里,我們重點討論一下category的優(yōu)缺點;
優(yōu)點:
1)在不改變一個類的情況下,對一個已存在的類添加新的方法。
2)可以再沒有源代碼的情況下,對框架中的類進(jìn)行擴展。
3)當(dāng)一個類中的代碼量太大時,可以按功能將代碼中的方法放入到不同的category中,減小單個文件的體積。
缺點:
1)類別中的方法的優(yōu)先級高于類中的方法,所以類別中的方法有可能會覆蓋類中的方法。(因此在使用category時,需要注意命名,不要重復(fù)了)
2)不能直接添加成員變量。
3)可以添加屬性,但是不會自動生成getter和setter方法。

category的實現(xiàn)原理

category是Objective-C的語言特性,探討其實現(xiàn)原理,需要從runtime源碼下手,下面就借助源碼簡單分析一下其實現(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ì)都是一個結(jié)構(gòu)體;這個結(jié)構(gòu)體中定義了name、cls、實例方法、類方法、協(xié)議、屬性等等,所以按理說,分類其實是可以為類添加方法、屬性、協(xié)議的,但是不能添加實例變量,因為這個結(jié)構(gòu)體中沒有定義用來存放實力變量的指針變量。

category加載過程

category的加載過程,相對來說就比較復(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);
}

前面幾個函數(shù)的調(diào)用,其實都是準(zhǔn)備工作,最主要的還是:
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
當(dāng)然,我們并不需要去研究dyld的加載過程,所以我們并不關(guān)注這個函數(shù)的具體實現(xiàn),我們關(guān)心的,是這個函數(shù)的第一個參數(shù)map_images。他是一個回調(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);
}

這個函數(shù)比較簡單,就兩個函數(shù)的調(diào)用,第一個lock,猜測應(yīng)該是和線程有關(guān)的,不做深入研究,主要看第二個函數(shù)map_images_nolock。
這個函數(shù)有點長,但是其實我們并不需要對其深入研究,因為這個函數(shù)并不是加載category的細(xì)節(jié),我們找到這個函數(shù)調(diào)用:

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

_read_images函數(shù)也很長,里面有對類、協(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);
                }
            }

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

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

這個函數(shù)可能看起來有點暈乎,但是仔細(xì)研究,其實也很簡單:
首先,通過unattachedCategories ()取出runtime維護(hù)的MapTable -----category_map,這個MapTable是以TargetClass為key,category_list結(jié)構(gòu)體對象為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ù)組來存儲每一個category_t對象的;這也解釋了為什么一個類可以定義多個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的注冊過程:
判斷以TargetClass為key從MapTable中取出的類型為category_listlist指針是否為空,如果為空,則為list指針申請空間;如果不為空,則在list所占用的空間上,追加申請一個大小為sizeof(list->list[0])的空間,最后,將catcatHeader存儲到list->list數(shù)組中。
以上就是category的注冊過程,接下來看看方法的重新綁定過程:

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

這個函數(shù)寥寥數(shù)語,其實也很好搞懂,通過目標(biāo)類名,獲取unattachedcategory_list,并調(diào)用attachCategories函數(shù),將category_list綁定到目標(biāo)類上。
這個綁定過程,涉及到屬性、協(xié)議以及方法的綁定,這里,我們重點分析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);

這段代碼其實就做了一件事:將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]));
        }
    }

這里粗略的看看三個if else語句內(nèi)的代碼,其實不難看出,這三個地方,都是在做同一個操作-----將addedLists中的數(shù)據(jù),拷貝到array()->lists。我們第一個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]));
        }

首先,計算出新的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])個字節(jié):

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

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

至此,整個category的分析過程就算完成了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容