天天看點

runtime源碼探究(五)category的加載

本文擴充自:http://blog.sunnyxx.com/2014/08/30/objc-pre-main/ 這篇文章主要描述了iOS平台上main函數調用之前所發生的事。我們從這裡開始講述category是如何加載的。

main函數開始之前,在一些準備工作之後,libSystem會調用

void _objc_init(void)
           

函數,這裡便是runtime的入口,也就是這時候啟動了runtime。蘋果自己的注釋也描述的很清楚

/*************************************************************

* _objc_init

* Bootstrap initialization. Registers our image notifier with dyld.

* Called by libSystem BEFORE library initialization time

蘋果在這個函數裡面做了一些初始化的工作,包括每個類+load方法的調用,關于load方法的調用順序如下:

do {
        // 1. Repeatedly call class +loads until there         aren't any more
        while (loadable_classes_used > ) {
            call_class_loads();
        }

        // 2. Call category +loads ONCE
        more_categories = call_category_loads();

        // 3. Run more +loads if there are classes OR more untried categories
    } while (loadable_classes_used >   ||  more_categories);
           

接下來蘋果調用了map_2_images函數,函數裡面在最後又調用了_read_images函數,該函數通過_objc_read_categories_from_image實作category的加載。該函數的實作如下:

static bool _objc_read_categories_from_image (header_info *  hi)
{
    Module      mods;
    size_t  midx;
    bool needFlush = NO;

    if (hi->info()->isReplacement()) {
        // Ignore any categories in this image
        return NO;
    }

    // Major loop - process all modules in the header
    mods = hi->mod_ptr;

    // NOTE: The module and category lists are traversed backwards 
    // to preserve the pre-10.4 processing order. Changing the order 
    // would have a small chance of introducing binary compatibility bugs.
    midx = hi->mod_count;
    while (midx-- > ) {
        unsigned int    index;
        unsigned int    total;

        // Nothing to do for a module without a symbol table
        if (mods[midx].symtab == nil)
            continue;

        // Total entries in symbol table (class entries followed
        // by category entries)
        total = mods[midx].symtab->cls_def_cnt +
            mods[midx].symtab->cat_def_cnt;

        // Minor loop - register all categories from given module
        index = total;
        while (index-- > mods[midx].symtab->cls_def_cnt) {
            old_category *cat = (old_category *)mods[midx].symtab->defs[index];
            needFlush |= _objc_register_category(cat, (int)mods[midx].version);
        }
    }

    return needFlush;
}

           

首先判斷該類是否忽略加載category,如果忽略就直接傳回;然後從傳入的hi(其中存儲的是各個加載的子產品的資訊)參數中周遊要加載的category的所有子產品;首先判斷子產品的符号表是否為空

// Nothing to do for a module without a symbol table
        if (mods[midx].symtab == nil)
            continue;
           

如果為空就不需要加載了;接下來從該子產品的符号表中取出category的數量和類的數量,然後周遊該子產品所有的category,調用_objc_register_category函數進行加載。該函數的實作如下:

static bool _objc_register_category(old_category *cat, int version)
{
    _objc_unresolved_category * new_cat;
    _objc_unresolved_category * old;
    Class theClass;

    // If the category's class exists, attach the category.
    if ((theClass = objc_lookUpClass(cat->class_name))) {
        return _objc_add_category_flush_caches(theClass, cat, version);
    }

    // If the category's class exists but is unconnected, 
    // then attach the category to the class but don't bother 
    // flushing any method caches (because they must be empty).
    // YES unconnected, NO class_handler
    if ((theClass = look_up_class(cat->class_name, YES, NO))) {
        _objc_add_category(theClass, cat, version);
        return NO;
    }


    // Category's class does not exist yet. 
    // Save the category for later attachment.

    if (PrintConnecting) {
        _objc_inform("CONNECT: pending category '%s (%s)'", cat->class_name, cat->category_name);
    }

    // Create category lookup table if needed
    if (!category_hash)
        category_hash = NXCreateMapTable(NXStrValueMapPrototype, );

    // Locate an existing list of categories, if any, for the class.
    old = (_objc_unresolved_category *)
        NXMapGet (category_hash, cat->class_name);

    // Register the category to be fixed up later.
    // The category list is built backwards, and is reversed again 
    // by resolve_categories_for_class().
    new_cat = (_objc_unresolved_category *)
        malloc(sizeof(_objc_unresolved_category));
    new_cat->next    = old;
    new_cat->cat     = cat;
    new_cat->version = version;
    (void) NXMapKeyCopyingInsert (category_hash, cat->class_name, new_cat);

    return NO;
}
           

蘋果對該函數也進行了說明:

/***********************************************************************
* _objc_register_category.
* Process a category read from an image. 
* If the category's class exists, attach the category immediately. 
*   Classes that need cache flushing are marked but not flushed.
* If the category's class does not exist yet, pend the category for 
*   later attachment. Pending categories are attached in the order 
*   they were discovered.
* Returns YES if some method caches now need to be flushed.
**********************************************************************/
           

如果category對應的類存在,那麼就立即加載category;如果對應的類不存在,那麼将category挂起等待後續加載;如果類的一些方法緩存需要重新整理,但是重新整理行為不在該函數中完成,該函數傳回YES。

_objc_register_category函數首先去尋找category對應的類

// If the category's class exists, attach the category.
    if ((theClass = objc_lookUpClass(cat->class_name))) {
        return _objc_add_category_flush_caches(theClass, cat, version);
    }

    // If the category's class exists but is unconnected, 
    // then attach the category to the class but don't bother 
    // flushing any method caches (because they must be empty).
    // YES unconnected, NO class_handler
    if ((theClass = look_up_class(cat->class_name, YES, NO))) {
        _objc_add_category(theClass, cat, version);
        return NO;
    }

           

如果找到了,就加載category到對應的類,如果沒找到,就建立一個hash表,将這個category存起來,待之後系統去加載。

category的加載通過_objc_add_category_flush_caches函數實作:

/***********************************************************************
* _objc_add_category_flush_caches.  Install the specified category's 
* methods into the class it augments, and flush the class' method cache.
* Return YES if some method caches now need to be flushed.
**********************************************************************/
static bool _objc_add_category_flush_caches(Class cls, old_category *category, int version)
{
    bool needFlush = NO;

    // Install the category's methods into its intended class
    {
        mutex_locker_t lock(methodListLock);
        _objc_add_category (cls, category, version);
    }

    // Queue for cache flushing so category's methods can get called
    if (category->instance_methods) {
        cls->setInfo(CLS_FLUSH_CACHE);
        needFlush = YES;
    }
    if (category->class_methods) {
        cls->ISA()->setInfo(CLS_FLUSH_CACHE);
        needFlush = YES;
    }

    return needFlush;
}
           

這裡面主要就是調用_objc_add_category函數,該函數的實作如下:

/***********************************************************************
* _objc_add_category.  Install the specified category's methods and
* protocols into the class it augments.
* The class is assumed not to be in use yet: no locks are taken and 
* no method caches are flushed.
**********************************************************************/
static inline void _objc_add_category(Class cls, old_category *category, int version)
{
    if (PrintConnecting) {
        _objc_inform("CONNECT: attaching category '%s (%s)'", cls->name, category->category_name);
    }

    // Augment instance methods
    if (category->instance_methods)
        _objc_insertMethods (cls, category->instance_methods, category);

    // Augment class methods
    if (category->class_methods)
        _objc_insertMethods (cls->ISA(), category->class_methods, category);

    // Augment protocols
    if ((version >= ) && category->protocols)
    {
        if (cls->ISA()->version >= )
        {
            category->protocols->next = cls->protocols;
            cls->protocols            = category->protocols;
            cls->ISA()->protocols       = category->protocols;
        }
        else
        {
            _objc_inform ("unable to add protocols from category %s...\n", category->category_name);
            _objc_inform ("class `%s' must be recompiled\n", category->class_name);
        }
    }

    // Augment instance properties
    if (version >=   &&  category->instance_properties) {
        if (cls->ISA()->version >= ) {
            _class_addProperties(cls, category->instance_properties);
        } else {
            _objc_inform ("unable to add instance properties from category %s...\n", category->category_name);
            _objc_inform ("class `%s' must be recompiled\n", category->class_name);
        }
    }

    // Augment class properties
    if (version >=   &&  category->hasClassPropertiesField()  &&  
        category->class_properties) 
    {
        if (cls->ISA()->version >= ) {
            _class_addProperties(cls->ISA(), category->class_properties);
        } else {
            _objc_inform ("unable to add class properties from category %s...\n", category->category_name);
            _objc_inform ("class `%s' must be recompiled\n", category->class_name);
        }
    }
}
           

這裡主要是做了三件事:

1、将category的執行個體方法和類方法添加到類中

2、将category的protocol添加到類中

3、将category的執行個體對象和類對象添加到類中

至此,runtime幫助我們完成了所有的category的加載。

最後附上category的結構,其實也十分簡單:

struct old_category {
    char *category_name;
    char *class_name;
    struct old_method_list *instance_methods;
    struct old_method_list *class_methods;
    struct old_protocol_list *protocols;
    // Fields below this point are in version 7 or later only.
    uint32_t size;
    struct old_property_list *instance_properties;
    // Check size for fields below this point.
    struct old_property_list *class_properties;

    bool hasClassPropertiesField() const { 
        return size >= offsetof(old_category, class_properties) + sizeof(class_properties);
    }
};