iOS進階之路 (十一)分類的加載

上篇文章講到,實現了+ load方法的類是非懶加載類,不然就是懶加載類。面試

  1. 非懶加載類:+ load方法是在main函數以前被調用的。這個時候爲了能後保證+ load方法能被調用,就必須提早把這個類加載好。
  • 非懶加載類加載流程: _dyld_objc_notify_register() -> read_image -> realizeClassWithoutSwift) -> methodizeClass->attachLists對rw賦值。
  1. 懶加載:顧名思義,是平時不會被加載,只有在用到的時候纔會被加載。

那麼懶記載類是如何加載的呢?swift

一. 懶加載類的加載

在咱們第一次使用這個類的時候,也就是給這個類發送第一條消息的時候,懶加載的類纔會被真正加載。數組

在以前的篇章中咱們也講到過消息發送,消息發送中有一個很重要的方法lookUpImpOrForward。咱們提到過 !cls->isRealized()用來初始化懶加載類的。在Object-C環境下,通過一系列的函數調用,會神奇的來到了咱們上篇文章學習的realizeClassWithoutSwift緩存

IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                       bool initialize, bool cache, bool resolver)
{
    ...
    if (!cls->isRealized()) {
        cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
        // runtimeLock may have been dropped but is now locked again
    }
    ...
}

static Class
realizeClassMaybeSwiftAndLeaveLocked(Class cls, mutex_t& lock)
{
    return realizeClassMaybeSwiftMaybeRelock(cls, lock, true);
}

static Class
realizeClassMaybeSwiftAndLeaveLocked(Class cls, mutex_t& lock)
{
    return realizeClassMaybeSwiftMaybeRelock(cls, lock, true);
}

static Class
realizeClassMaybeSwiftMaybeRelock(Class cls, mutex_t& lock, bool leaveLocked)
{
    lock.assertLocked();

    if (!cls->isSwiftStable_ButAllowLegacyForNow()) {
        // Non-Swift class. Realize it now with the lock still held.
        // fixme wrong in the future for objc subclasses of swift classes
        realizeClassWithoutSwift(cls);
        if (!leaveLocked) lock.unlock();
    } else {
        // Swift class. We need to drop locks and call the Swift
        // runtime to initialize it.
        lock.unlock();
        cls = realizeSwiftClass(cls);
        assert(cls->isRealized());    // callback must have provoked realization
        if (leaveLocked) lock.lock();
    }

    return cls;
}
複製代碼

咱們來測試下,懶加載類能不能知足!cls->isRealized()條件bash

1.1 測試一

AKPerson沒有實現 + load 方法,是懶加載類,主程序調用[AKPerson alloc]的初始化方法。app

結論一:懶加載類會在第一次調用的時候進行加載,加載的時機是在消息查找流程中的lookUpImpOrForward方法中。函數

1.2 測試二

父類AKPerson實現 + load 方法,子類AKStudnet不實現 + load 方法。清理緩存,主程序子類調用初始化方法。post

結論二:父類實現+ load, 子類不實現+ load。父類是非懶加載類,子類是懶加載類。學習

1.3 測試三

父類AKPerson不實現 + load 方法,子類AKStudnet實現 + load 方法。清理緩存,主程序子類先調用的初始化方法,父類再調用的初始化方法。測試

發現父類沒有進入!cls->isRealized(), 父類是懶加載類。由於遞歸調用realizeClassWithoutSwift完善繼承鏈並處理當前類的父類、元類;若是有父類,就經過addSubclass把當前類放到父類的子類列表中去

if (!cls) return nil;
...
supercls = realizeClassWithoutSwift(remapClass(cls->superclass));
metacls = realizeClassWithoutSwift(remapClass(cls->ISA()));
...
// Update superclass and metaclass in case of remapping
cls->superclass = supercls;
cls->initClassIsa(metacls);
...
// Connect this class to its superclass`s subclass lists
if (supercls) {
    addSubclass(supercls, cls);
} else {
    addRootClass(cls);
}
複製代碼

結論三:若是子類實現+ load,那麼父類也會在子類被加載的時候,一塊兒被加載。緣由是子類在加載的時候會對父類和元類進行處理。

二. 分類的結構

2.1 clang

新建AKPerson + Test分類

clang -rewrite-objc AKPerson+Test.m -o category.cpp ,打開cpp文件能夠發現。

  • category存儲在MachO文件的__DATA的__objc_catlist
static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [1] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= {
	&_OBJC_$_CATEGORY_AKPerson_$_Test,
};
複製代碼
  • AKPerson分類的結構以下
static struct _category_t _OBJC_$_CATEGORY_AKPerson_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = 
{
	"AKPerson",
	0, // &OBJC_CLASS_$_AKPerson,
	(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_AKPerson_$_Test,
	(const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_AKPerson_$_Test,
	0,
	0,
};
複製代碼

2.2 分類的結構

objc源碼中搜索category_t

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);
};
複製代碼

爲何分類要將實例方法和類方法分開保存呢?

類和元類加載過程當中不斷編譯,實例方法存在類中,類方法存在元類中,已經肯定好其方法歸屬的地方;而分類晚於類和元類的加載。

三.分類的加載

咱們如今知道了類分爲了懶加載類非懶加載類,它們的加載時機是不同的,那麼分類的加載又是怎麼樣的呢?

在分析前,還要搞清楚一點,分類必須依附於類而存在,若是隻有分類,沒有類,那麼從邏輯上是說不通的,就算實現了,編譯器也會忽略掉。

分類的加載在兩處出現過:

  • _read_imagesDiscover categories.
  • methodizeClass
// Discover categories.
// 發現和處理全部Category
for (EACH_HEADER) {
    // 外部循環遍歷找到當前類,查找類對應的Category數組
    category_t **catlist = 
        _getObjc2CategoryList(hi, &count);
    bool hasClassProperties = hi->info()->hasCategoryClassProperties();

    for (i = 0; i < count; i++) {
        // 內部循環遍歷當前類的全部Category
        category_t *cat = catlist[i];
        Class cls = remapClass(cat->cls);
        
        // 首先,經過其所屬的類註冊Category。若是這個類已經被實現,則從新構造類的方法列表。
        bool classExists = NO;
        if (cat->instanceMethods ||  cat->protocols  
            ||  cat->instanceProperties) 
        {
            // 將Category添加到對應Class的value中,value是Class對應的全部category數組
            addUnattachedCategoryForClass(cat, cls, hi);
            // 將Category的method、protocol、property添加到Class
            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" : "");
            }
        }

        // 這塊和上面邏輯同樣,區別在於這塊是對Meta Class作操做,而上面則是對Class作操做
        // 根據下面的邏輯,從代碼的角度來講,是能夠對原類添加Category的
        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);
            }
        }
    }
}
複製代碼
static void methodizeClass(Class cls)
{
    ...
    // Attach categories.
    category_list *cats = unattachedCategoriesForClass(cls, true /*realizing*/);
    attachCategories(cls, cats, false /*don't flush caches*/); ... } 複製代碼

爲了方便定位,咱們添加了一些調試代碼

3.1 懶加載分類

懶加載類 & 懶加載分類

經過函數調用棧分析:

  • 向懶加載類發送消息,lookupOrForward -> realizeClassWithoutSwift開始加載內存
  • methodizeClass處理父類、元類關係
  • unattachedCategoriesForClass返回NULL
  • 另外一處加載分類沒有調用

非懶加載 & 懶加載分類

經過兩次的斷點調試,咱們發現懶加載的分類,在運行時期間沒有進行添加分類的操做,咱們來看看分類中的方法是否被添加進來。

  • 程序啓動dyld -> _objc_init -> map_images -> _read_images -> realizeClassWithoutSwift ->methodizeClass 加載類到內存中
  • methodizeClass處理父類、元類關係
  • unattachedCategoriesForClas返回NULL
  • 另外一處加載分類沒有調用

懶加載的分類不是運行時添加的,咱們來看看分類中的方法是否被添加進來。

  • 查看一下class_rw_t中的內容
(lldb) p *$3
(class_rw_t) $4 = {
  flags = 2148139008
  version = 7
  ro = 0x00000001000011f0
  methods = {
    list_array_tt<method_t, method_list_t> = {
       = {
        list = 0x0000000100001168
        arrayAndFlag = 4294971752
      }
    }
  }
  properties = {
    list_array_tt<property_t, property_list_t> = {
       = {
        list = 0x0000000000000000
        arrayAndFlag = 0
      }
    }
  }
  protocols = {
    list_array_tt<unsigned long, protocol_list_t> = {
       = {
        list = 0x0000000000000000
        arrayAndFlag = 0
      }
    }
  }
  firstSubclass = nil
  nextSiblingClass = 0x00007fff92d22080
  demangledName = 0x0000000000000000
}
複製代碼
  • 繼續查看ro中的baseMethodList
(lldb) p $8.get(1)
(method_t) $15 = {
  name = "load"
  types = 0x0000000100000f8c "v16@0:8"
  imp = 0x0000000100000c10 (objc-debug`+[AKPerson load] at AKPerson.m:12)
}
(lldb) p $8.get(2)
(method_t) $16 = {
  name = "cate_instanceMethod"
  types = 0x0000000100000f8c "v16@0:8"
  imp = 0x0000000100000da0 (objc-debug`+[AKPerson(test) cate_instanceMethod] at AKPerson+test.m:34)
}
複製代碼

經過上述的lldb調試,咱們發現,咱們分類中的方法已經被添加到ro中了。

結論:不論是懶加載類或是非懶加載類,懶加載分類在編譯時就肯定了。

3.2 非懶加載分類

懶加載類 & 非懶加載分類

按照以前的理論,懶加載的類是在第一次發送消息的時候纔會被加載的,函數調用棧應該是lookupImpOrForward -> realizeClassMaybeSwiftAndLeaveLocked -> realizeClassMaybeSwiftMaybeRelock -> realizeClassWithoutSwift -> methodizeClass。咱們測試下。

這一次經過 unattachedCategoriesForClass 取到值了,而且在這以前 cls 的 ro 中並無分類的 initialize 方法:

可是咱們的函數調用棧,不是發送消息的流程,而走的是 load_imagesprepare_load_methods 方法呢?

  • 懶加載類要在消息發送的時候纔會加載。
  • 可是分類是非懶加載類,分類會提早走 read_images -> addUnattachedCategoryForClass
  • 此時沒有實現類 ,會在下面的prepare_load_methods -> realizeClassWithoutSwift -> unattachedCategoriesForClass提早了實現類的信息
/***********************************************************************
* load_images
* Process +load in the given images which are being mapped in by dyld.
*
* Locking: write-locks runtimeLock and loadMethodLock
**********************************************************************/
extern bool hasLoadMethods(const headerType *mhdr);
extern void prepare_load_methods(const headerType *mhdr);

void
load_images(const char *path __unused, const struct mach_header *mh)
{
    // Return without taking locks if there are no +load methods here.
    if (!hasLoadMethods((const headerType *)mh)) return;

    recursive_mutex_locker_t lock(loadMethodLock);

    // Discover load methods
    {
        mutex_locker_t lock2(runtimeLock);
        prepare_load_methods((const headerType *)mh);
    }

    // Call +load methods (without runtimeLock - re-entrant)
    call_load_methods();
}

void prepare_load_methods(const headerType *mhdr)
{
    size_t count, i;

    runtimeLock.assertLocked();
    
    // 獲取的全部的非懶加載分類
    classref_t *classlist = 
        _getObjc2NonlazyClassList(mhdr, &count);
    for (i = 0; i < count; i++) {
        schedule_class_load(remapClass(classlist[i]));
    }

    category_t **categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
    for (i = 0; i < count; i++) {
        category_t *cat = categorylist[i];
        Class cls = remapClass(cat->cls);
        if (!cls) continue;  // category for ignored weak-linked class
        if (cls->isSwiftStable()) {
            _objc_fatal("Swift class extensions and categories on Swift "
                        "classes are not allowed to have +load methods");
        }
        realizeClassWithoutSwift(cls);
        assert(cls->ISA()->isRealized());
        add_category_to_loadable_list(cat);
    }
}
複製代碼
  • _getObjc2NonlazyCategoryList獲取的全部的非懶加載分類,而後遍歷這些非懶加載分類,加載這些分類所依賴的類。
  • realizeClassWithoutSwift 方法來加載類

結論:非懶加載分類讓咱們的懶加載類實現提早了,因此說懶加載類並不必定只會在第一次消息發送的時候加載,還要取決於有沒有非懶加載的分類,若是有非懶加載的分類,那麼就走的是 load_images 裏面的 prepare_load_methodsrealizeClassWithoutSwift

非懶加載類 & 非懶加載分類

非懶加載類的流程咱們十分熟悉了,在 _read_images 裏面進行加載,而此時,分類也是非懶加載。

  1. methodizeClass 處斷點:

unattachedCategoriesForClass 取出來的是 NULL,顯然分類不是在這個地方被加載的

  1. _read_imagesDiscover categories 處斷點

由於當前類已經在前面的非懶加載類加載流程中被加載完成,因此會進入 remethodizeClass 方法

/***********************************************************************
* 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);
    }
}
複製代碼
  1. remethodizeClass 有一個 attachCategories方法
// Attach method lists and properties and protocols from categories to a class.
// Assumes the categories in cats are all loaded and sorted by load order, 
// oldest categories first.
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];

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

        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);
}
複製代碼

注意英文註釋:

  • Attach method lists and properties and protocols from categories to a class. -- 將分類的方法、屬性和協議添加到類上
  • Assumes the categories in cats are all loaded and sorted by load order, -- 分類按加載順序加載完畢
  • oldest categories first. 先加載的分類排在前面

attachCategoriesattachLists原理基本一致 (參考類的加載):

  • 調用attachLists添加分類的方法、屬性、協議
  • memmove將原數據移到末尾
  • memcpy把新數據拷貝到起始位置

  • 其實 attachCategories 這個方法只會在實現了 非懶加載分類 下才會被調用,而來到 attachCategories 以前又取決於類是否爲懶加載,
  • 若是是懶加載,那麼就在 load_images 裏面去處理,
  • 若是是非懶加載,那麼就在 read_images 裏面去處理。

四. 分類總結

  1. 類的加載
  • 非懶加載類:+ load方法是在main函數以前被調用的。這個時候爲了能後保證+ load方法能被調用,就必須提早把這個類加載好。
  • 懶加載:顧名思義,是平時不會被加載,只有在用到的時候纔會被加載
  1. 分類的加載:
  • 非懶加載分類:沒有實現 load方法,編譯時肯定, 直接處理 data() - ro。
  • 懶加載分離:實現了 load 方法,運行時肯定。
  1. 這也說明分類的加載和類的加載是不同的,二者結合,咱們有如下的結論:
情景 類的加載 分類的加載
懶加載分類 + 懶加載類 第一次發送 編譯時
懶加載分類 + 非懶加載類 _read_images 編譯時
非懶加載分類 + 懶加載類 load_images(非懶加載分類讓咱們的懶加載類實現提早了) 類加載以後的 methodizeClass
非懶加載分類 + 非懶加載類 _read_images 類加載以後的 reMethodizeClass

五. 類和分類的同名方法之爭

若是類有多個分類,方法調用順序如何呢?

Person類AKPerson+Test1AKPerson+Test1兩個分類,三者都聲明和實現類- sayHi方法,主程序調用[[AKPerson alloc] sayHi];。

  1. 分類都不實現 + load 方法

響應Compile Sources最後一個分類

  1. 分類都實現 + load 方法

響應 Compile Sources最後一個分類

  1. AKPerson+Test1實現 + load 方法, AKPerson+Test2不實現 + load 方法

  1. AKPerson+Test2實現 + load 方法, AKPerson+Test1不實現 + load 方法

響應實現 + load 方法的分類。

結論1

通常方法先調用分類,後調用主類。

  • 分類的方法沒有替換掉類已經有的方法, 分類的方法被放到了新方法列表的前面,而類的方法被放到了新方法列表的後面,這也就是咱們日常所說的分類的方法會「覆蓋」掉類的同名方法
  • 由於運行時在查找方法時是順着方法列表的順序查找的,它只要一找到對應名字的方法,就會返回imp。

結論2

  • 若是分類沒實現+load方法,就響應Compile Sources最後一個分類
  • 若是都實現+load,響應·Compile Sources·最後一個分類
  • 若是其中一個實現了+load方法,響應非懶加載分類。由於懶加載分類在編譯時就已經加載到內存,而非懶加載分類運行時才加載

六. load_images

懶加載類 + 非懶加載分類狀況下,分類加載到內存時會調用load_image,那麼咱們在該種狀況下進行探索.

load_image實現處打下斷點,發現類和分類都沒有打印+load方法:load_image先於+load方法

注意英文註釋:

  • Discover load methods -- prepare_load_methods
  • Call +load methods (without runtimeLock - re-entrant) -- call_load_methods

6.1 prepare_load_methods 發現並準備+load方法

void prepare_load_methods(const headerType *mhdr)
{
    size_t count, i;

    runtimeLock.assertLocked();

    // 1.獲取非懶加載類列表
    classref_t *classlist = 
        _getObjc2NonlazyClassList(mhdr, &count);
    for (i = 0; i < count; i++) {
        schedule_class_load(remapClass(classlist[i]));
    }

    // 2.獲取非懶加載分類列表
    category_t **categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
    for (i = 0; i < count; i++) {
        category_t *cat = categorylist[i];
        Class cls = remapClass(cat->cls);
        
        const class_ro_t *ro = (const class_ro_t *)cls->data();
        const char *cname = ro->name;
        const char *oname = "AKPerson";
        
        if (cname && (strcmp(cname, oname) == 0)) {
            printf("_getObjc2NonlazyClassList 類名 :%s - %p 分類名: %s\n",cname,cls,cat->name);
        }
        
        if (!cls) continue;  // category for ignored weak-linked class
        if (cls->isSwiftStable()) {
            _objc_fatal("Swift class extensions and categories on Swift "
                        "classes are not allowed to have +load methods");
        }
        realizeClassWithoutSwift(cls);
        assert(cls->ISA()->isRealized());
        add_category_to_loadable_list(cat);
    }
}
複製代碼
/***********************************************************************
* prepare_load_methods
* Schedule +load for classes in this image, any un-+load-ed 
* superclasses in other images, and any categories in this image.
**********************************************************************/
// Recursively schedule +load for cls and any un-+load-ed superclasses.
// cls must already be connected.
static void schedule_class_load(Class cls)
{
    if (!cls) return;
    assert(cls->isRealized());  // _read_images should realize

    if (cls->data()->flags & RW_LOADED) return;

    // Ensure superclass-first ordering
    schedule_class_load(cls->superclass);

    add_class_to_loadable_list(cls);
    cls->setInfo(RW_LOADED); 
}
複製代碼
/***********************************************************************
* add_category_to_loadable_list
* Category cat`s parent class exists and the category has been attached
* to its class. Schedule this category for +load after its parent class
* becomes connected and has its own +load method called.
**********************************************************************/
void add_category_to_loadable_list(Category cat)
{
    IMP method;

    loadMethodLock.assertLocked();

    method = _category_getLoadMethod(cat);

    // Don`t bother if cat has no +load method
    if (!method) return;

    if (PrintLoading) {
        _objc_inform("LOAD: category '%s(%s)' scheduled for +load", 
                     _category_getClassName(cat), _category_getName(cat));
    }
    
    if (loadable_categories_used == loadable_categories_allocated) {
        loadable_categories_allocated = loadable_categories_allocated*2 + 16;
        loadable_categories = (struct loadable_category *)
            realloc(loadable_categories,
                              loadable_categories_allocated *
                              sizeof(struct loadable_category));
    }

    loadable_categories[loadable_categories_used].cat = cat;
    loadable_categories[loadable_categories_used].method = method;
    loadable_categories_used++;
}
複製代碼

prepare_load_methods 分析:

  1. _getObjc2NonlazyClassList 獲取非懶加載類列表
  2. schedule_class_load遍歷類列表
  • 遞歸調用父類的 + load 方法,保證父類的 + load 方法順序排列在子類前面
  • add_class_to_loadable_list類的+load方法存在loadable_classes裏面
  1. _getObjc2NonlazyCategoryList 獲取非懶加載分類列表
  2. 遍歷分類列表
  • realizeClassWithoutSwift 來防止類沒有初始化(若已經初始化了則不影響)
  • add_category_to_loadable_list分類的+load方法到loadable_categories

6.2 call_load_methods

如今咱們知道+ loadload_images裏調用,到底怎麼調用的呢?

/***********************************************************************
* call_load_methods
* Call all pending class and category +load methods.
* Class +load methods are called superclass-first. 
* Category +load methods are not called until after the parent class`s +load.
* 
* This method must be RE-ENTRANT, because a +load could trigger 
* more image mapping. In addition, the superclass-first ordering 
* must be preserved in the face of re-entrant calls. Therefore, 
* only the OUTERMOST call of this function will do anything, and 
* that call will handle all loadable classes, even those generated 
* while it was running.
*
* The sequence below preserves +load ordering in the face of 
* image loading during a +load, and make sure that no 
* +load method is forgotten because it was added during 
* a +load call.
* Sequence:
* 1. Repeatedly call class +loads until there aren`t any more
* 2. Call category +loads ONCE.
* 3. Run more +loads if:
*    (a) there are more classes to load, OR
*    (b) there are some potential category +loads that have 
*        still never been attempted.
* Category +loads are only run once to ensure "parent class first" 
* ordering, even if a category +load triggers a new loadable class 
* and a new loadable category attached to that class. 
*
* Locking: loadMethodLock must be held by the caller 
*   All other locks must not be held.
**********************************************************************/
void call_load_methods(void)
{
    static bool loading = NO;
    bool more_categories;

    loadMethodLock.assertLocked();

    // Re-entrant calls do nothing; the outermost call will finish the job.
    if (loading) return;
    loading = YES;

    void *pool = objc_autoreleasePoolPush();

    do {
        // 1. Repeatedly call class +loads until there aren`t any more
        while (loadable_classes_used > 0) {
            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 > 0  ||  more_categories);

    objc_autoreleasePoolPop(pool);

    loading = NO;
}
複製代碼
static void call_class_loads(void)
{
    ...
    
    // Call all +loads for the detached list.
    for (i = 0; i < used; i++) {
        Class cls = classes[i].cls;
        load_method_t load_method = (load_method_t)classes[i].method;
        if (!cls) continue; 

        if (PrintLoading) {
            _objc_inform("LOAD: +[%s load]\n", cls->nameForLogging());
        }
        (*load_method)(cls, SEL_load);
    }
    
    ...
}
複製代碼
static bool call_category_loads(void)
{
    ...
    
    // Call all +loads for the detached list.
    for (i = 0; i < used; i++) {
        Category cat = cats[i].cat;
        load_method_t load_method = (load_method_t)cats[i].method;
        Class cls;
        if (!cat) continue;

        cls = _category_getClass(cat);
        if (cls  &&  cls->isLoadable()) {
            if (PrintLoading) {
                _objc_inform("LOAD: +[%s(%s) load]\n", 
                             cls->nameForLogging(), 
                             _category_getName(cat));
            }
            (*load_method)(cls, SEL_load);
            cats[i].cat = nil;
        }
    }

    ...
    
    return new_categories_added;
}
複製代碼
  1. 經過objc_autoreleasePoolPush壓棧一個自動釋放池
  2. do-while循環
  • 循環調用call_load_methods, 發送消息調用類的+load方法。
  • 調用call_category_loads,循環發送消息調用分類的+load方法。
  • (*load_method)(cls, SEL_load);調用+ load的過程,就是objc_msgSend(cls, SEL_load)的過程
  1. 經過 objc_autoreleasePoolPop出棧一個自動釋放池+load方法

load_images流程圖

七. initialize

7.1 initialize原理

Initializes the class before it receives its first message.

在這個類接收第一條消息以前調用。當該類不使用時,該方法可能永遠不會被調用。

lookUpImpOrForward -> initializeAndLeaveLocked -> initializeAndMaybeRelock -> initializeNonMetaClass找到了它的蹤影。

/***********************************************************************
* class_initialize.  Send the '+initialize' message on demand to any
* uninitialized class. Force initialization of superclasses first.
**********************************************************************/
void initializeNonMetaClass(Class cls)
{
    ...
    
    // Make sure super is done initializing BEFORE beginning to initialize cls.
    // See note about deadlock above.
    supercls = cls->superclass;
    if (supercls  &&  !supercls->isInitialized()) {
        initializeNonMetaClass(supercls);
    }
    
    ...
    callInitialize(cls);
    ...
}
複製代碼
void callInitialize(Class cls)
{
    ((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
    asm("");
}
複製代碼
  1. 若是有父類 且 父類沒有isInitialized,遞歸initializeNonMetaClass父類(推測:調用順序 先父類後子類)
  2. callInitialize是一個普通的消息發送(推測:調用順序 分類覆蓋主類)

7.2 initialize 調用順序測試

  1. 是否是先父類後子類

AKPerson父類AKTeacher子類都實現initialize方法

  • 主程序 先調用父類,後調用子類

  • 主程序 先調用子類, 後調用父類

  1. 是否是分類覆蓋主類

AKPerson + Test分類AKTeacher + Test分類都實現initialize方法,主程序前後調用子類父類初始化方法。

  1. 是否是隻調用一次

AKPerson父類實現initialize方法,AKTeacher子類不實現initialize方法,主程序調用子類初始化方法。

7.3 initialize 總結

  1. initialize走普通的消息發送機制。因此分類覆蓋主類,當有多個分類都實現了initialize方法,執行最後被加載到內存中的分類的方法。
  2. initialize在類或者其子類的第一個方法被調用前(發送消息前)調用
  3. 若是父類和子類都實現了initialize方法,在調用子類時,
  • 若是父類的initialize方法調用過,則只調用子類的initialize方法;
  • 若是父類的initialize沒用過,則先調用父類的initialize方法,在調用子類的initialize方法。(此時,再初始化父類的時候,不會再調用initialize方法)
  1. 父類實現,子類不實現,調用子類時,會調用兩次父類的initialize方法

八. 總結

本篇主要學習了 懶加載類 非懶加載類 懶加載分類 非懶加載分類的加載; + load+ initialize 的調用。也是面試中百分比會被問到的地方,但願有所幫助。

相關文章
相關標籤/搜索