iOS Category源碼分析

什麼是Category?

CategoryObjective-C 2.0以後添加的語言特性,它的主要做用是爲已經存在的類添加方法,通常稱爲分類。 CategoryiOS開發中使用很是的頻繁,特別是在爲系統類進行拓展的時候,咱們能夠不用繼承系統類,直接給系統類添加方法,最大程度的體現了Objective-C的動態語言特性。數組

Category結構體

新建了一個工程,建立了一個實體類,和一個category分類 bash

cmd+B編譯以後打開終端,cd到工程的目錄,執行 clang -rewrite-objc Test+categroy.m命令
同個目錄下會生成 Test+categroy.cpp文件
打開以後搜索 _category_t,看到編譯出來的category結構體
查看源碼中的objc_runtime_new.h文件就能夠看到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的底層結構category_t和上文中咱們獲得的_category_t結構體基本一致,在編譯以後,每一個Category確實會生成一個category_t類型的結構體。並且category_t類型的結構體中是沒有ivars這項,這就是分類是不能添加成員變量的緣由。數據結構

Category加載流程

dyld加載

  • dyld是蘋果的動態加載器,用來加載imageimage指的是Mach-O格式的二進制文件,不是圖片)
  • 當程序啓動時,系統內核首先會加載dyld,而dyld會將咱們APP所依賴的各類庫加載到內存中,其中就包括libobjc庫(OCruntime),這些工做,是在APPmain函數執行以前完成的
  • _objc_initObject-C runtime 的入口函數,在這裏主要是讀取Mach-O文件OC對應的Segment section,並根據其中的數據代碼信息,完成爲OC的內存佈局,以及初始化runtime相關的數據結構。

_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);
}
複製代碼
  • 調用一些init函數以後,重點在_dyld_objc_notify_register這一句代碼,這裏註冊了三個函數
  • &map_imagesimage加載進內存
  • load_imagesdyld初始化加載image方法
  • unmap_images移除內存 下面探究map_images這個函數
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);
}
複製代碼
  • map_images_nolock函數是用來執行全部類註冊和修復操做,並調用+load方法
map_images_nolock(unsigned mhCount, const char * const mhPaths[],
                  const struct mach_header * const mhdrs[])
{
    ...
    //前面的代碼省略,主要代碼是_read_images這個函數
    if (hCount > 0) {
        _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
    }

    firstTime = NO;
}
複製代碼
  • _read_images函數中有多個段落,咱們研究的重點在於分類
// Discover categories. 
    for (EACH_HEADER) {
        // 獲取項目中全部的Category,獲得一個二維數組
        category_t **catlist = 
            _getObjc2CategoryList(hi, &count);
        bool hasClassProperties = hi->info()->hasCategoryClassProperties();
        // 遍歷二維數組,獲得每個category_t類型的結構體變量
        for (i = 0; i < count; i++) {
            category_t *cat = catlist[i];
            // 經過cat->cls拿到當前Category所屬的類
            Class cls = remapClass(cat->cls);
            
            if (!cls) {
                catlist[i] = nil;
                if (PrintConnecting) {
                    _objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with "
                                 "missing weak-linked target class", 
                                 cat->name, cat);
                }
                continue;
            }
            // 判斷class是否存在
            bool classExists = NO;
            // 若是Category中存在實例方法,協議或者是實例屬性
            if (cat->instanceMethods ||  cat->protocols  
                ||  cat->instanceProperties) 
            {
                // 將cls的未合併的全部Category存放到以cls爲key的一個映射表中去
                addUnattachedCategoryForClass(cat, cls, hi);
                if (cls->isRealized()) {
                    // 從新組織實例方法,將Category中的方法、屬性、協議等等附加到cls的實例方法列表、實例屬性列表和協議列表中去
                    remethodizeClass(cls);
                    classExists = YES;
                }
                if (PrintConnecting) {
                    _objc_inform("CLASS: found category -%s(%s) %s", 
                                 cls->nameForLogging(), cat->name, 
                                 classExists ? "on existing class" : "");
                }
            }
            // 若是Category中存在類方法,協議或者是類屬性
            if (cat->classMethods  ||  cat->protocols  
                ||  (hasClassProperties && cat->_classProperties)) 
            {
                addUnattachedCategoryForClass(cat, cls->ISA(), hi);
                // 從新組織元類方法,將Category中的類方法、類屬性、協議等等附加到cls的元類的類方法列表、類屬性列表和協議列表中去
                if (cls->ISA()->isRealized()) {
                    remethodizeClass(cls->ISA());
                }
                if (PrintConnecting) {
                    _objc_inform("CLASS: found category +%s(%s)", 
                                 cls->nameForLogging(), cat->name);
                }
            }
        }
    }
複製代碼
  • 進入remethodizeClass函數,重點函數在attachCategories
static void remethodizeClass(Class cls)
{
    category_list *cats;
    bool isMeta;

    runtimeLock.assertWriting();

    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);
    }
}
複製代碼
  • 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];
        // 取出分類中的方法存放到二維數組 mlists 中,isMeta決定是類方法仍是實例方法
        method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
        if (mlist) {
            mlists[mcount++] = mlist;
            fromBundle |= entry.hi->isBundle();
        }
        // 取出分類中的屬性存放到二維數組 proplists 中
        property_list_t *proplist = 
            entry.cat->propertiesForMeta(isMeta, entry.hi);
        if (proplist) {
            proplists[propcount++] = proplist;
        }
        // 取出分類中的協議存放到二維數組 protolists 中
        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);
}
複製代碼
  • attachLists這個函數主要是關聯原類方法的
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]));
        }
複製代碼
  • 能夠看出,分類的方法附加的類對象中的時候,只是把分類的方法放到原來的方法前面,這樣runtime查找方法的時候就會先調用分類的方法

參考文章

手把手帶你探索Category底層原理
CATEGORY實現的原理一:底層結構及源碼分析函數

相關文章
相關標籤/搜索