類加載原理(中)

1.jpeg

接續(xù)上一篇文章類加載原理(上)的內(nèi)容我們繼續(xù)探索妇汗。上一篇我們一直在尋找關于類Class的一些處理比如加載ro\rw跋破。所以我們在_read_images方法里把代碼根據(jù)不同功能區(qū)分了十個部分。并且根據(jù)我們的目的利用特殊打印排除的方法查找了幾個火脉。下面我們繼續(xù)查找關于類的代碼部分。

類的加載處理:

非懶加載類加載處理流程

我們同樣利用特殊打印排除的方法來看看我們的類ZYPerson是否會進入到這里。

void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
    /*
     * 省略上面的代碼
     */
   // Category discovery MUST BE Late to avoid potential races
    // when other threads call the new category code before
    // this thread finishes its fixups.

    // +load handled by prepare_load_methods()
   // Realize non-lazy classes (for +load methods and static instances)
    for (EACH_HEADER) {
        classref_t const *classlist = hi->nlclslist(&count);
        for (i = 0; i < count; i++) {
            Class cls = remapClass(classlist[i]);
            if (!cls) continue;

            /* *********************嘗試打印看看我們自己的類能不能進來*****************/
            const char *mangledName = cls->nonlazyMangledName();
            //定義自己的類名
            const char *ZYPersonName = "ZYPerson";
            //比較自己的類名和讀取的是否一致一致就進入if
            if (strcasecmp(ZYPersonName, mangledName) == 0) {
                printf("Realize non-lazy classes - ZY 我們需要跟蹤的信息: %s - - %s\n",__func__,mangledName);
            }
            /* *********************嘗試打印看看我們自己的類能不能進來*****************/
            
            addClassTableEntry(cls);

            if (cls->isSwiftStable()) {
                if (cls->swiftMetadataInitializer()) {
                    _objc_fatal("Swift class %s with a metadata initializer "
                                "is not allowed to be non-lazy",
                                cls->nameForLogging());
                }
                // fixme also disallow relocatable classes
                // We can't disallow all Swift classes because of
                // classes like Swift.__EmptyArrayStorage
            }
            realizeClassWithoutSwift(cls, nil);
        }
    }

    ts.log("IMAGE TIMES: realize non-lazy classes");
    /*
     * 省略下面的代碼
     */
}

我們嘗試跑起了我們的代碼曙蒸,發(fā)現(xiàn)并沒有直接進來我們的打印打碼润绵,并且我嘗試利用斷點來跟蹤也發(fā)現(xiàn)并沒有進這個方法线椰。但是這個方法明明是對類的加載的一些處理呀(看注釋)最后我發(fā)現(xiàn)注釋里有解釋那就是這段代碼是對非懶加載類的處理要走這里就先要使我們的類成為非懶加載的類,for +load methods and static instances所以我在我的ZYPerson類里實現(xiàn)了一下+ (void *)load{}方法使之成為非懶加載類尘盼。然后來跑代碼憨愉,誒?發(fā)現(xiàn)真的進來了卿捎。如下:

ZYPerson.h:

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface ZYPerson : NSObject

- (void)zyEatSugar;

+ (void)sayHappy;

- (void)zySay1;

- (void)zySay2;

- (void)zySay3;

- (void)zyShowTime;

@end

NS_ASSUME_NONNULL_END

ZYPerson.m:

#import "ZYPerson.h"
@implementation ZYPerson
+(void)load{
    
}

- (void)zyEatSugar
{
    NSLog(@"%s",__func__);
}

+ (void)sayHappy
{
    NSLog(@"%s",__func__);
}

- (void)zySay1
{
    NSLog(@"%s",__func__);
}

- (void)zySay2
{
    NSLog(@"%s",__func__);
}

- (void)zySay3
{
    NSLog(@"%s",__func__);
}

- (void)zyShowTime
{
    NSLog(@"%s",__func__);
}

main.m:

int main(int argc, const char * argv[]) {
    @autoreleasepool {

        ZYPerson *person = [ZYPerson alloc];
    
        //調(diào)用ZYPerson 的zyEatSugar 方法 配紫,該方法未實現(xiàn)
        [person zyEatSugar];

    }
    return 0;
}

打印結(jié)果:

Realize non-lazy classes - ZY 我們需要跟蹤的信息: _read_images - - ZYPerson
KCObjcBuild was compiled with optimization - stepping may behave oddly; variables may not be available.
2021-08-03 14:37:38.175895+0800 KCObjcBuild[25482:4921796] -[ZYPerson zyEatSugar]
Program ended with exit code: 0

發(fā)現(xiàn)是走了我們的打印的。所以我們再次回到這段代碼午阵。我們發(fā)現(xiàn)這段代碼其實就兩個方法是我們需要關注的其他的都是一些判斷和數(shù)據(jù)處理笨蚁。一個是addClassTableEntry(cls);這個方法我們在上一篇文章——類加載原理(上)中的readClass方法里去探索過,發(fā)現(xiàn)只是對類的名字/mangledName地址HashMap的形式添加進表的動作趟庄。通過這個方法我們只能把類名和地址關聯(lián)起來括细,但是它怎么實現(xiàn)的還沒看到。所以我們看另一個方法:realizeClassWithoutSwift(cls, nil);

1,realizeClassWithoutSwift(cls, nil);

/***********************************************************************
* realizeClassWithoutSwift
* Performs first-time initialization on class cls, 
* including allocating its read-write data.
* Does not perform any Swift-side initialization.
* Returns the real class structure for the class. 
* Locking: runtimeLock must be write-locked by the caller
**********************************************************************/
static Class realizeClassWithoutSwift(Class cls, Class previously)
{
    runtimeLock.assertLocked();

    class_rw_t *rw;
    Class supercls;
    Class metacls;

    if (!cls) return nil;
    if (cls->isRealized()) {
        validateAlreadyRealizedClass(cls);
        return cls;
    }
    ASSERT(cls == remapClass(cls));

    // fixme verify class is not in an un-dlopened part of the shared cache?
        
    auto ro = (const class_ro_t *)cls->data();
    auto isMeta = ro->flags & RO_META;
    if (ro->flags & RO_FUTURE) {
        // This was a future class. rw data is already allocated.
        rw = cls->data();
        ro = cls->data()->ro();
        ASSERT(!isMeta);
        cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
    } else {
        // Normal class. Allocate writeable class data.
        rw = objc::zalloc<class_rw_t>();
        rw->set_ro(ro);
        rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
        cls->setData(rw);
    }

    cls->cache.initializeToEmptyOrPreoptimizedInDisguise();

#if FAST_CACHE_META
    if (isMeta) cls->cache.setBit(FAST_CACHE_META);
#endif

    // Choose an index for this class.
    // Sets cls->instancesRequireRawIsa if indexes no more indexes are available
    cls->chooseClassArrayIndex();

    if (PrintConnecting) {
        _objc_inform("CLASS: realizing class '%s'%s %p %p #%u %s%s",
                     cls->nameForLogging(), isMeta ? " (meta)" : "", 
                     (void*)cls, ro, cls->classArrayIndex(),
                     cls->isSwiftStable() ? "(swift)" : "",
                     cls->isSwiftLegacy() ? "(pre-stable swift)" : "");
    }

    // Realize superclass and metaclass, if they aren't already.
    // This needs to be done after RW_REALIZED is set above, for root classes.
    // This needs to be done after class index is chosen, for root metaclasses.
    // This assumes that none of those classes have Swift contents,
    //   or that Swift's initializers have already been called.
    //   fixme that assumption will be wrong if we add support
    //   for ObjC subclasses of Swift classes.
    supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil);
    metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);

#if SUPPORT_NONPOINTER_ISA
    if (isMeta) {
        // Metaclasses do not need any features from non pointer ISA
        // This allows for a faspath for classes in objc_retain/objc_release.
        cls->setInstancesRequireRawIsa();
    } else {
        // Disable non-pointer isa for some classes and/or platforms.
        // Set instancesRequireRawIsa.
        bool instancesRequireRawIsa = cls->instancesRequireRawIsa();
        bool rawIsaIsInherited = false;
        static bool hackedDispatch = false;

        if (DisableNonpointerIsa) {
            // Non-pointer isa disabled by environment or app SDK version
            instancesRequireRawIsa = true;
        }
        else if (!hackedDispatch  &&  0 == strcmp(ro->getName(), "OS_object"))
        {
            // hack for libdispatch et al - isa also acts as vtable pointer
            hackedDispatch = true;
            instancesRequireRawIsa = true;
        }
        else if (supercls  &&  supercls->getSuperclass()  &&
                 supercls->instancesRequireRawIsa())
        {
            // This is also propagated by addSubclass()
            // but nonpointer isa setup needs it earlier.
            // Special case: instancesRequireRawIsa does not propagate
            // from root class to root metaclass
            instancesRequireRawIsa = true;
            rawIsaIsInherited = true;
        }

        if (instancesRequireRawIsa) {
            cls->setInstancesRequireRawIsaRecursively(rawIsaIsInherited);
        }
    }
// SUPPORT_NONPOINTER_ISA
#endif

    // Update superclass and metaclass in case of remapping
    cls->setSuperclass(supercls);
    cls->initClassIsa(metacls);

    // Reconcile instance variable offsets / layout.
    // This may reallocate class_ro_t, updating our ro variable.
    if (supercls  &&  !isMeta) reconcileInstanceVariables(cls, supercls, ro);

    // Set fastInstanceSize if it wasn't set already.
    cls->setInstanceSize(ro->instanceSize);

    // Copy some flags from ro to rw
    if (ro->flags & RO_HAS_CXX_STRUCTORS) {
        cls->setHasCxxDtor();
        if (! (ro->flags & RO_HAS_CXX_DTOR_ONLY)) {
            cls->setHasCxxCtor();
        }
    }
    
    // Propagate the associated objects forbidden flag from ro or from
    // the superclass.
    if ((ro->flags & RO_FORBIDS_ASSOCIATED_OBJECTS) ||
        (supercls && supercls->forbidsAssociatedObjects()))
    {
        rw->flags |= RW_FORBIDS_ASSOCIATED_OBJECTS;
    }

    // Connect this class to its superclass's subclass lists
    if (supercls) {
        addSubclass(supercls, cls);
    } else {
        addRootClass(cls);
    }

    // Attach categories
    methodizeClass(cls, previously);

    return cls;
}

這個方法我們先在開頭加入我們的特殊打印打碼戚啥,OB一下奋单,看看是否會進來,若是進來猫十,并且我們看到這里有ro的出現(xiàn)览濒。我們就查看下下面的ro是否存在。

2.png
3.png

從上面的操作截圖來看拖云,ZYPerson確實進來了這個方法贷笛,但是直接打印ro里的方法列表卻打印不出來。而從上面我們貼出來的代碼看我們的ZYPerson里是有方法的宙项。我們在main函數(shù)里還調(diào)用了乏苦。所以到這里我們的方法還沒有加載進來。我們接著往下找尤筐。

我們在下面的代碼繼續(xù)添加斷點來跟蹤下如下圖:

4.png

我們發(fā)現(xiàn)ZYPerson 走了eles 而我們在eles里看到了我們熟悉并且一直尋找的字眼ro汇荐、rw。我們在上面看到這里的ro 是從cls->data()里獲取的盆繁。而這里是又利用ro 進行處理賦值給我rw掀淘。我們就回顧去看看這個ro到底怎么獲取的。

auto ro = (const class_ro_t *)cls->data();

class_rw_t *data() const {
        return bits.data();
    }
class_rw_t* data() const {
      return (class_rw_t *)(bits & FAST_DATA_MASK);
}

看上面我們可以知道原來ro是直接讀取出來并且rw應該是包含ro的數(shù)據(jù)格式的油昂,因為他是以rw格式強轉(zhuǎn)出來的乳蓄。

在上面的eles代碼我們還看到了將讀取出來的ro直接利用set方法set進了一個新創(chuàng)建的rw里。然后判斷是否是元類來設置rwflags。最后將rw利用set方法set進了classdata()``里。這就是類加載的時候ro具壮、rw`的處理。

我們接著往下看:

下面就是對類的cache做初始化處理:

cls->cache.initializeToEmptyOrPreoptimizedInDisguise();

然后就是獲取到本類的父類和元類:

// Choose an index for this class.
    // Sets cls->instancesRequireRawIsa if indexes no more indexes are available
    cls->chooseClassArrayIndex();

    if (PrintConnecting) {
        _objc_inform("CLASS: realizing class '%s'%s %p %p #%u %s%s",
                     cls->nameForLogging(), isMeta ? " (meta)" : "", 
                     (void*)cls, ro, cls->classArrayIndex(),
                     cls->isSwiftStable() ? "(swift)" : "",
                     cls->isSwiftLegacy() ? "(pre-stable swift)" : "");
    }

    // Realize superclass and metaclass, if they aren't already.
    // This needs to be done after RW_REALIZED is set above, for root classes.
    // This needs to be done after class index is chosen, for root metaclasses.
    // This assumes that none of those classes have Swift contents,
    //   or that Swift's initializers have already been called.
    //   fixme that assumption will be wrong if we add support
    //   for ObjC subclasses of Swift classes.
    supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil);
    metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);

然后就是判斷是否是符合SUPPORT_NONPOINTER_ISA如果符合就走

#if SUPPORT_NONPOINTER_ISA
    if (isMeta) {
        // Metaclasses do not need any features from non pointer ISA
        // This allows for a faspath for classes in objc_retain/objc_release.
        cls->setInstancesRequireRawIsa();
    } else {
        // Disable non-pointer isa for some classes and/or platforms.
        // Set instancesRequireRawIsa.
        bool instancesRequireRawIsa = cls->instancesRequireRawIsa();
        bool rawIsaIsInherited = false;
        static bool hackedDispatch = false;

        if (DisableNonpointerIsa) {
            // Non-pointer isa disabled by environment or app SDK version
            instancesRequireRawIsa = true;
        }
        else if (!hackedDispatch  &&  0 == strcmp(ro->getName(), "OS_object"))
        {
            // hack for libdispatch et al - isa also acts as vtable pointer
            hackedDispatch = true;
            instancesRequireRawIsa = true;
        }
        else if (supercls  &&  supercls->getSuperclass()  &&
                 supercls->instancesRequireRawIsa())
        {
            // This is also propagated by addSubclass()
            // but nonpointer isa setup needs it earlier.
            // Special case: instancesRequireRawIsa does not propagate
            // from root class to root metaclass
            instancesRequireRawIsa = true;
            rawIsaIsInherited = true;
        }

        if (instancesRequireRawIsa) {
            cls->setInstancesRequireRawIsaRecursively(rawIsaIsInherited);
        }
    }
// SUPPORT_NONPOINTER_ISA
#endif

在這里判斷是否是元類,分別對類做處理。然后就到了下面的代碼:

// Update superclass and metaclass in case of remapping
    cls->setSuperclass(supercls);
    cls->initClassIsa(metacls);

    // Reconcile instance variable offsets / layout.
    // This may reallocate class_ro_t, updating our ro variable.
    if (supercls  &&  !isMeta) reconcileInstanceVariables(cls, supercls, ro);

    // Set fastInstanceSize if it wasn't set already.
    cls->setInstanceSize(ro->instanceSize);

    // Copy some flags from ro to rw
    if (ro->flags & RO_HAS_CXX_STRUCTORS) {
        cls->setHasCxxDtor();
        if (! (ro->flags & RO_HAS_CXX_DTOR_ONLY)) {
            cls->setHasCxxCtor();
        }
    }
    
    // Propagate the associated objects forbidden flag from ro or from
    // the superclass.
    if ((ro->flags & RO_FORBIDS_ASSOCIATED_OBJECTS) ||
        (supercls && supercls->forbidsAssociatedObjects()))
    {
        rw->flags |= RW_FORBIDS_ASSOCIATED_OBJECTS;
    }

    // Connect this class to its superclass's subclass lists
    if (supercls) {
        addSubclass(supercls, cls);
    } else {
        addRootClass(cls);
    }

在這里就根據(jù)前面獲取的父類元類然后把本類元類以及父類都做了相應的關聯(lián)處理我衬。這里就是我們常用到的isa走位圖的實現(xiàn)和繼承鏈的實現(xiàn)了叹放。

最后我們看到還剩一行代碼:

// Attach categories
    methodizeClass(cls, previously);
    return cls;

2,methodizeClass:

在這行代碼我們看名稱是對方法的處理挠羔。我們剛在前面利用ro打印方法列表的時候并未打印出來井仰。因為那個時候的類還只有一個名稱和一個地址關聯(lián),并沒有方法ro破加、rw的處理俱恶。但是到這行代碼我們不禁想,經(jīng)過上面的對類的一些列處理范舀,這里是否有了方法呢合是?下面我們進入這個方法并且利用我們的特殊打印來打印一下

/***********************************************************************
* methodizeClass
* Fixes up cls's method list, protocol list, and property list.
* Attaches any outstanding categories.
* Locking: runtimeLock must be held by the caller
**********************************************************************/
static void methodizeClass(Class cls, Class previously)
{
    runtimeLock.assertLocked();

    bool isMeta = cls->isMetaClass();
    auto rw = cls->data();
    auto ro = rw->ro();
    auto rwe = rw->ext();

    
    /* *********************嘗試打印看看我們自己的類能不能進來*****************/
    const char *mangledName = cls->nonlazyMangledName();
    //定義自己的類名
    const char *ZYPersonName = "ZYPerson";
    //定義變量來存儲本次進來的是否是元類
    auto zy_ro = (const class_ro_t *)cls->data();
    auto zy_isMeta = zy_ro->flags & RO_META;
    
    //比較自己的類名和讀取的是否一致一致就進入if
    if (strcasecmp(ZYPersonName, mangledName) == 0) {
        if (!zy_isMeta) {//判斷不是元類就打印
            printf("realizeClassWithoutSwift---2222 -- ZY 我們需要跟蹤的信息: %s - - %s\n",__func__,mangledName);
        }
    }
    /* *********************嘗試打印看看我們自己的類能不能進來*****************/
    /*
     * 省略下面的代碼
    */

我們排除元類后就直接打印斷點打印代碼,然后去lldb調(diào)試查看是否有方法锭环。

Realize non-lazy classes - ZY 我們需要跟蹤的信息: _read_images - - ZYPerson
realizeClassWithoutSwift - ZY 我們需要跟蹤的信息: realizeClassWithoutSwift - - ZYPerson
realizeClassWithoutSwift - ZY 我們需要跟蹤的信息: realizeClassWithoutSwift - - ZYPerson
(lldb) p ro
(const class_ro_t *) $0 = 0x00000001000081c8
(lldb) p *40
error: <user expression 1>:1:1: indirection requires pointer operand ('int' invalid)
*40
^~~
(lldb) p *$0
(const class_ro_t) $1 = {
  flags = 0
  instanceStart = 8
  instanceSize = 40
  reserved = 0
   = {
    ivarLayout = 0x0000000000000000
    nonMetaclass = nil
  }
  name = {
    std::__1::atomic<const char *> = "ZYPerson" {
      Value = 0x0000000100003f07 "ZYPerson"
    }
  }
  baseMethodList = 0x0000000100008210
  baseProtocols = nil
  ivars = 0x0000000100008290
  weakIvarLayout = 0x0000000000000000
  baseProperties = 0x0000000100008318
  _swiftMetadataInitializer_NEVER_USE = {}
}
(lldb) p $1->baseMethodList
(void *const) $2 = 0x0000000100008210
  Fix-it applied, fixed expression was: 
    $1.baseMethodList
(lldb) p *$2
(lldb) p *$2
(lldb) 

從上面的lldb調(diào)試發(fā)現(xiàn)到這個方法開始ro里都沒有方法列表聪全。所以我們接著往下看。

在這個方法里我們打印的下方有對方法的處理

// Install methods and properties that the class implements itself.
    method_list_t *list = ro->baseMethods();
    if (list) {
        prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls), nullptr);
        if (rwe) rwe->methods.attachLists(&list, 1);
    }

這里直接去獲取baseMethods如果不為空就去做一些預備的處理進入方法prepareMethodLists辅辩。所以我們跟蹤到這個處理方法看看

3难礼,prepareMethodLists:

static void 
prepareMethodLists(Class cls, method_list_t **addedLists, int addedCount,
                   bool baseMethods, bool methodsFromBundle, const char *why)
{
    runtimeLock.assertLocked();

    if (addedCount == 0) return;

    // There exist RR/AWZ/Core special cases for some class's base methods.
    // But this code should never need to scan base methods for RR/AWZ/Core:
    // default RR/AWZ/Core cannot be set before setInitialized().
    // Therefore we need not handle any special cases here.
    if (baseMethods) {
        ASSERT(cls->hasCustomAWZ() && cls->hasCustomRR() && cls->hasCustomCore());
    } else if (cls->cache.isConstantOptimizedCache()) {
        cls->setDisallowPreoptCachesRecursively(why);
    } else if (cls->allowsPreoptInlinedSels()) {
#if CONFIG_USE_PREOPT_CACHES
        SEL *sels = (SEL *)objc_opt_offsets[OBJC_OPT_INLINED_METHODS_START];
        SEL *sels_end = (SEL *)objc_opt_offsets[OBJC_OPT_INLINED_METHODS_END];
        if (method_lists_contains_any(addedLists, addedLists + addedCount, sels, sels_end - sels)) {
            cls->setDisallowPreoptInlinedSelsRecursively(why);
        }
#endif
    }

    // Add method lists to array.
    // Reallocate un-fixed method lists.
    // The new methods are PREPENDED to the method list array.

    for (int i = 0; i < addedCount; i++) {
        method_list_t *mlist = addedLists[I];
        ASSERT(mlist);

        // Fixup selectors if necessary
        if (!mlist->isFixedUp()) {
            fixupMethodList(mlist, methodsFromBundle, true/*sort*/);
        }
    }

    // If the class is initialized, then scan for method implementations
    // tracked by the class's flags. If it's not initialized yet,
    // then objc_class::setInitialized() will take care of it.
    if (cls->isInitialized()) {
        objc::AWZScanner::scanAddedMethodLists(cls, addedLists, addedCount);
        objc::RRScanner::scanAddedMethodLists(cls, addedLists, addedCount);
        objc::CoreScanner::scanAddedMethodLists(cls, addedLists, addedCount);
    }
}

在這個方法我們看到前面有一些判斷和處理 但是重點是在于中間的for循環(huán)。我們在for循環(huán)里打個斷點然后進行lldb調(diào)試看看到底是怎么處理和進入的玫锋。如圖5

5.png

經(jīng)過上面斷點調(diào)試和輸出我們發(fā)現(xiàn)確實進入了這個for循環(huán)并且進入了方法fixupMethodList進行了方法修復蛾茉。我們就跟蹤到這個方法去看看他的實現(xiàn):

4,fixupMethodList

static void 
fixupMethodList(method_list_t *mlist, bool bundleCopy, bool sort)
{
    runtimeLock.assertLocked();
    ASSERT(!mlist->isFixedUp());

    // fixme lock less in attachMethodLists ?
    // dyld3 may have already uniqued, but not sorted, the list
    if (!mlist->isUniqued()) {
        mutex_locker_t lock(selLock);
    
        // Unique selectors in list.
        for (auto& meth : *mlist) {
            const char *name = sel_cname(meth.name());
            meth.setName(sel_registerNameNoLock(name, bundleCopy));
        }
    }

    // Sort by selector address.
    // Don't try to sort small lists, as they're immutable.
    // Don't try to sort big lists of nonstandard size, as stable_sort
    // won't copy the entries properly.
    if (sort && !mlist->isSmallList() && mlist->entsize() == method_t::bigSize) {
        method_t::SortBySELAddress sorter;
        std::stable_sort(&mlist->begin()->big(), &mlist->end()->big(), sorter);
    }
    
    // Mark method list as uniqued and sorted.
    // Can't mark small lists, since they're immutable.
    if (!mlist->isSmallList()) {
        mlist->setFixedUp();
    }
}

方法分析:
第一步:是進行了for循環(huán)的遍歷去獲取到了每一個方法的名字然后利用注冊方法sel_registerNameNoLock進行了名字sel綁定注冊最后利用set方法set進了method撩鹿。
第二步:進行了sort排序谦炬。根據(jù)注釋我們知道他不會對small lists方法進行排序因為他不可變。
第三步:給非small lists方法列表打標記調(diào)用setFixedUp方法节沦。

我們不妨來打印下吧寺,看看排序前和排序后的方法名稱和地址。同時為了控制使我們自己的類的方法我們在上面的methodizeClass方法添加的打印里打上一個斷點散劫,只有來到這個斷點的時候證明是我們自己的類ZYPerson的方法稚机。這個時候我們再清空打印,并且在for循環(huán)打印的代碼后面加上一個斷點(此斷點隨意打在打印后的代碼就可)為了避免系統(tǒng)后面的方法也打印出來获搏。

讓我們要觀察的方法打印出來赖条。如圖6:

6.png

打印結(jié)果:如圖7

7.png

到這里我們prepareMethodLists 方法的主要流程就走完了失乾。

下面我們回到prepareMethodLists 方法入口methodizeClass方法里。在下一行代碼打上斷點查看走向纬乍。如圖8

8.png

我們看到rwe在這個方法里為NULL碱茁。所以不會走下面的if方法attachLists。那么這個rwe是什么時候才會賦值的呢仿贬?我們放到文章后面來探討纽竣。

至此我們可以理一遍流程:
第一步:從_read_images 方法進入 查找到關于類的方法readClass進行了類的名字和類的地址綁定;

第二步:走到另一部分代碼 進行非懶加載類的加載處理 然后進入方法 realizeClassWithoutSwift進行ro茧泪、rw處理和父類蜓氨、元類isa走位綁定處理队伟。然后進入方法處理方法methodizeClass;
第三步:在這個方法進行方法穴吹、屬性分類的處理嗜侮,我們只是跟蹤到了方法處理港令,進入到了方法預處理方法prepareMethodLists
第四步:對方法進行遍歷然后調(diào)用方法修復方法fixupMethodList
第五步:fixupMethodList方法對方法進行名字sel綁定,并且排序處理锈颗。
這就是上面我們探索的內(nèi)容顷霹。

總結(jié):回歸到我們之前為我們的類添加load方法,我們不加load方法是不會走我們read_images里的非懶加載類的處理流程击吱。蘋果這樣做的目的就是為了節(jié)約內(nèi)存泼返,為了使得在開發(fā)過程中的類得到區(qū)分,把一些類定義為懶加載類(沒有實現(xiàn)load方法的)姨拥,在這些懶加載類沒有用到之前都不會去加載和處理绅喉。這樣就會使得內(nèi)存得到大量的結(jié)余。啟動時間更短運行更快叫乌。

疑問:既然上面我們談到非懶加載類會走上面的處理流程柴罐,那懶加載類呢?懶加載類怎么處理的呢憨奸?

懶加載類加載處理流程

我們從上面非懶加載類的處理流程從read_images里的非懶加載類判斷入口進入到realizeClassWithoutSwift方法革屠。之后進行一些列的類處理。那我們猜測是否在其他的地方也有有入口進入realizeClassWithoutSwift這個方法呢排宰?我們就在這個方法里之前添加的特殊打印的地方加一個斷點似芝。并且把ZYPersonload方法屏蔽掉.如果真的來了,那我們就利用bt命令來查看堆棧信息板甘,追溯流程党瓮。

9.png
10.png

從堆棧流程我們發(fā)現(xiàn)是:lookUpImpOrForward->realizeAndInitializeIfNeeded_locked->initializeAndLeaveLocked->initializeAndMaybeRelock->realizeClassMaybeSwiftAndUnlock->realizeClassMaybeSwiftMaybeRelock->realizeClassWithoutSwift
從這個流程進入了我們上面分析的類加載處理流程。

那么是什么時機調(diào)用的上面的流程呢盐类?

分析:我們從上面可以知道寞奸,當進入上面bt流程的時候ZYPerson只是進行了alloc呛谜,并沒有去調(diào)用下面的方法。這時候就已經(jīng)進入到這里了枪萄。

結(jié)論:所以我們可以確定懶加載類是在第一次消息發(fā)送的時候就會去加載這個相關的類隐岛。

總結(jié):

下面我用兩張圖片來總結(jié)歸納懶加載和非懶加載類的加載處理過程:

類加載流程.png

分類(categofies)提前預告:

我們在上面探索類的加載流程的時候在methodizeClass方法里根據(jù)斷點跟蹤看到預處理方法列表后有判斷rwe是否存在,那個時候我們發(fā)現(xiàn)是不存在的瓷翻,那影響這個的因素是什么呢聚凹?而且我們在進入methodizeClass方法前看到他的注釋是Attach categories這不得不讓我們引發(fā)思考就是這里面的某些東西是否是跟分類有關的呢?下面我們來到main.m文件在main函數(shù)上方添加一個分類ZYPerson (ZY)給這個類設置兩個屬性和三個方法齐帚。然后我們利用 clang -rewrite-objc main.m -o main.cpp命令將main.m文件轉(zhuǎn)成c++文件看看其真實存在是什么樣的妒牙。

main.m

@interface ZYPerson (ZY)

@property (nonatomic, copy) NSString *zy_name;
@property (nonatomic, assign) int zy_age;

- (void)zy_instanceMethod1;
- (void)zy_instanceMethod2;
+ (void)zy_classMethod3;

@end

@implementation ZYPerson (ZY)
- (void)zy_instanceMethod1
{
    NSLog(@"%s",__func__);
}
- (void)zy_instanceMethod2
{
    NSLog(@"%s",__func__);
}
+ (void)zy_classMethod3
{
    NSLog(@"%s",__func__);
}
@end

main.cpp:

我們直接command+下來到最后一行代碼,然后我們可以看到這樣的一行代碼:

static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [1] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= {
    &_OBJC_$_CATEGORY_ZYPerson_$_ZY,
};

在這里我們看到_CATEGORY_ZYPerson_拼接一個_ZY,就是我們自己創(chuàng)建的那個ZYPerson(ZY)的分類童谒。我們搜索下_category_t這個結(jié)構(gòu)體

_category_t:

struct _category_t {
    const char *name;
    struct _class_t *cls;
    const struct _method_list_t *instance_methods;
    const struct _method_list_t *class_methods;
    const struct _protocol_list_t *protocols;
    const struct _prop_list_t *properties;
};

在_category_t結(jié)構(gòu)體的成員變量里看到了一個名字name,應該就是存儲ZY這個字符;還有class沪羔,應該就是ZYPerson 類饥伊;然后就是實例方法類方法蔫饰、協(xié)議屬性琅豆。

在這里我們發(fā)現(xiàn)類方法居然是這些實例方法放一起的。不過我們想到分類是沒有元類這一點也就能夠理解了篓吁。因為沒有元類所以他的類方法只能直接和實例方法放在一起了茫因。

我們繼續(xù)搜索下_category_t分類的東西

static struct _category_t _OBJC_$_CATEGORY_ZYPerson_$_ZY __attribute__ ((used, section ("__DATA,__objc_const"))) = 
{
    "ZYPerson",
    0, // &OBJC_CLASS_$_ZYPerson,
    (const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_ZYPerson_$_ZY,
    (const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_ZYPerson_$_ZY,
    (const struct _protocol_list_t *)&_OBJC_CATEGORY_PROTOCOLS_$_ZYPerson_$_ZY,
    (const struct _prop_list_t *)&_OBJC_$_PROP_LIST_ZYPerson_$_ZY,
};

在這個我們自己分類ZYPerson(ZY)的結(jié)構(gòu)體中看到有一個名字記錄的居然是ZYPerson?然后class記錄的居然是個0?其實這里應該只是做一個占位作用杖剪,因為class不可能為0冻押,而那個name也不應該是ZYPerson 而應該是ZY,唯一的解釋就是在編譯階段還不能確定這個名字和類盛嘿。所以只是占位洛巢。然后看到有實例方法類方法次兆、協(xié)議屬性稿茉,和上面的結(jié)構(gòu)體模型結(jié)構(gòu)是一致的。

我們繼續(xù)查看下關于分類的東西


static struct /*_method_list_t*/ {
    unsigned int entsize;  // sizeof(struct _objc_method)
    unsigned int method_count;
    struct _objc_method method_list[2];
} _OBJC_$_CATEGORY_INSTANCE_METHODS_ZYPerson_$_ZY __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    2,
    {{(struct objc_selector *)"zy_instanceMethod1", "v16@0:8", (void *)_I_ZYPerson_ZY_zy_instanceMethod1},
    {(struct objc_selector *)"zy_instanceMethod2", "v16@0:8", (void *)_I_ZYPerson_ZY_zy_instanceMethod2}}
};

static struct /*_method_list_t*/ {
    unsigned int entsize;  // sizeof(struct _objc_method)
    unsigned int method_count;
    struct _objc_method method_list[1];
} _OBJC_$_CATEGORY_CLASS_METHODS_ZYPerson_$_ZY __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    1,
    {{(struct objc_selector *)"zy_classMethod3", "v16@0:8", (void *)_C_ZYPerson_ZY_zy_classMethod3}}
};

從上面的兩段代碼我們可以發(fā)現(xiàn)芥炭,雖然分類的實例方法和類方法放在一起漓库,但是他們在實現(xiàn)的時候是分開的。

協(xié)議_protocol_t:

struct _protocol_t _OBJC_PROTOCOL_NSObject __attribute__ ((used)) = {
    0,
    "NSObject",
    0,
    (const struct method_list_t *)&_OBJC_PROTOCOL_INSTANCE_METHODS_NSObject,
    0,
    (const struct method_list_t *)&_OBJC_PROTOCOL_OPT_INSTANCE_METHODS_NSObject,
    0,
    (const struct _prop_list_t *)&_OBJC_PROTOCOL_PROPERTIES_NSObject,
    sizeof(_protocol_t),
    0,
    (const char **)&_OBJC_PROTOCOL_METHOD_TYPES_NSObject
};
struct _protocol_t *_OBJC_LABEL_PROTOCOL_$_NSObject = &_OBJC_PROTOCOL_NSObject;

static struct /*_protocol_list_t*/ {
    long protocol_count;  // Note, this is 32/64 bit
    struct _protocol_t *super_protocols[1];
} _OBJC_CATEGORY_PROTOCOLS_$_ZYPerson_$_ZY __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    1,
    &_OBJC_PROTOCOL_NSObject
};

屬性_prop_list_t:

static struct /*_prop_list_t*/ {
    unsigned int entsize;  // sizeof(struct _prop_t)
    unsigned int count_of_properties;
    struct _prop_t prop_list[2];
} _OBJC_$_PROP_LIST_ZYPerson_$_ZY __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_prop_t),
    2,
    {{"zy_name","T@\"NSString\",C,N"},
    {"zy_age","Ti,N"}}
};

在查找屬性的時候我們并沒有發(fā)現(xiàn)他的setter/getter方法园蝠。這也從側(cè)面反映了我們經(jīng)常提到的分類不能設置屬性的說法渺蒿,是因為它不會生成setter/getter方法,所以我們才用關聯(lián)對象的方法來實現(xiàn)彪薛。

我們發(fā)現(xiàn)分類元類在編譯階段就已經(jīng)做了一些處理蘸嘶,尤其在類方法良瞧、屬性等特殊點上做了特殊的處理。所以我們下一篇文章就一起去探索下分類的一些實現(xiàn)原理训唱。

至此褥蚯,文章告一段落,原創(chuàng)碼字不易况增,如能給您帶來些許啟發(fā)那也是給作者的極大鼓勵赞庶。也盼望需要轉(zhuǎn)載的朋友請標注出處,謝謝澳骤!

遇事不決歧强,可問春風。站在巨人的肩膀上學習为肮,如有疏忽或者錯誤的地方還請多多指教摊册。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市颊艳,隨后出現(xiàn)的幾起案子茅特,更是在濱河造成了極大的恐慌,老刑警劉巖棋枕,帶你破解...
    沈念sama閱讀 221,273評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件白修,死亡現(xiàn)場離奇詭異,居然都是意外死亡重斑,警方通過查閱死者的電腦和手機兵睛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,349評論 3 398
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來窥浪,“玉大人祖很,你說我怎么就攤上這事⊙” “怎么了突琳?”我有些...
    開封第一講書人閱讀 167,709評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長符相。 經(jīng)常有香客問我拆融,道長,這世上最難降的妖魔是什么啊终? 我笑而不...
    開封第一講書人閱讀 59,520評論 1 296
  • 正文 為了忘掉前任镜豹,我火速辦了婚禮,結(jié)果婚禮上蓝牲,老公的妹妹穿的比我還像新娘趟脂。我一直安慰自己,他們只是感情好例衍,可當我...
    茶點故事閱讀 68,515評論 6 397
  • 文/花漫 我一把揭開白布昔期。 她就那樣靜靜地躺著已卸,像睡著了一般。 火紅的嫁衣襯著肌膚如雪硼一。 梳的紋絲不亂的頭發(fā)上累澡,一...
    開封第一講書人閱讀 52,158評論 1 308
  • 那天,我揣著相機與錄音般贼,去河邊找鬼愧哟。 笑死,一個胖子當著我的面吹牛哼蛆,可吹牛的內(nèi)容都是我干的蕊梧。 我是一名探鬼主播,決...
    沈念sama閱讀 40,755評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼腮介,長吁一口氣:“原來是場噩夢啊……” “哼肥矢!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起叠洗,我...
    開封第一講書人閱讀 39,660評論 0 276
  • 序言:老撾萬榮一對情侶失蹤甘改,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后惕味,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體楼誓,經(jīng)...
    沈念sama閱讀 46,203評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡玉锌,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,287評論 3 340
  • 正文 我和宋清朗相戀三年名挥,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片主守。...
    茶點故事閱讀 40,427評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡禀倔,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出参淫,到底是詐尸還是另有隱情救湖,我是刑警寧澤,帶...
    沈念sama閱讀 36,122評論 5 349
  • 正文 年R本政府宣布涎才,位于F島的核電站鞋既,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏耍铜。R本人自食惡果不足惜邑闺,卻給世界環(huán)境...
    茶點故事閱讀 41,801評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望棕兼。 院中可真熱鬧陡舅,春花似錦、人聲如沸伴挚。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,272評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至颅眶,卻和暖如春蜈出,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背帚呼。 一陣腳步聲響...
    開封第一講書人閱讀 33,393評論 1 272
  • 我被黑心中介騙來泰國打工掏缎, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人煤杀。 一個月前我還...
    沈念sama閱讀 48,808評論 3 376
  • 正文 我出身青樓眷蜈,卻偏偏與公主長得像,于是被迫代替她去往敵國和親沈自。 傳聞我的和親對象是個殘疾皇子酌儒,可洞房花燭夜當晚...
    茶點故事閱讀 45,440評論 2 359

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