类加载原理(中)

1.jpeg

接续上一篇文章类加载原理(上)的内容我们继续探索。上一篇我们一直在寻找关于类Class的一些处理比如加载ro\rw。所以我们在_read_images方法里把代码根据不同功能区分了十个部分。并且根据我们的目的利用特殊打印排除的方法查找了几个。下面我们继续查找关于类的代码部分。

类的加载处理:

非懒加载类加载处理流程

我们同样利用特殊打印排除的方法来看看我们的类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");
    /*
     * 省略下面的代码
     */
}

我们尝试跑起了我们的代码,发现并没有直接进来我们的打印打码,并且我尝试利用断点来跟踪也发现并没有进这个方法。但是这个方法明明是对类的加载的一些处理呀(看注释)最后我发现注释里有解释那就是这段代码是对非懒加载类的处理要走这里就先要使我们的类成为非懒加载的类,for +load methods and static instances所以我在我的ZYPerson类里实现了一下+ (void *)load{}方法使之成为非懒加载类。然后来跑代码,诶?发现真的进来了。如下:

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];
    
        //调用ZYPerson 的zyEatSugar 方法 ,该方法未实现
        [person zyEatSugar];

    }
    return 0;
}

打印结果:

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

发现是走了我们的打印的。所以我们再次回到这段代码。我们发现这段代码其实就两个方法是我们需要关注的其他的都是一些判断和数据处理。一个是addClassTableEntry(cls);这个方法我们在上一篇文章——类加载原理(上)中的readClass方法里去探索过,发现只是对类的名字/mangledName地址HashMap的形式添加进表的动作。通过这个方法我们只能把类名和地址关联起来,但是它怎么实现的还没看到。所以我们看另一个方法: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的出现。我们就查看下下面的ro是否存在。

2.png
3.png

从上面的操作截图来看,ZYPerson确实进来了这个方法,但是直接打印ro里的方法列表却打印不出来。而从上面我们贴出来的代码看我们的ZYPerson里是有方法的。我们在main函数里还调用了。所以到这里我们的方法还没有加载进来。我们接着往下找。

我们在下面的代码继续添加断点来跟踪下如下图:

4.png

我们发现ZYPerson 走了eles 而我们在eles里看到了我们熟悉并且一直寻找的字眼rorw。我们在上面看到这里的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的数据格式的,因为他是以rw格式强转出来的。

在上面的eles代码我们还看到了将读取出来的ro直接利用set方法set进了一个新创建的rw里。然后判断是否是元类来设置rwflags。最后将rw利用set方法set进了classdata()``里。这就是类加载的时候rorw`的处理。

我们接着往下看:

下面就是对类的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);
    }

在这里就根据前面获取的父类元类然后把本类元类以及父类都做了相应的关联处理。这里就是我们常用到的isa走位图的实现和继承链的实现了。

最后我们看到还剩一行代码:

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

2,methodizeClass:

在这行代码我们看名称是对方法的处理。我们刚在前面利用ro打印方法列表的时候并未打印出来。因为那个时候的类还只有一个名称和一个地址关联,并没有方法ro、rw的处理。但是到这行代码我们不禁想,经过上面的对类的一些列处理,这里是否有了方法呢?下面我们进入这个方法并且利用我们的特殊打印来打印一下

/***********************************************************************
* 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调试查看是否有方法。

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调试发现到这个方法开始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循环。我们在for循环里打个断点然后进行lldb调试看看到底是怎么处理和进入的。如图5

5.png

经过上面断点调试和输出我们发现确实进入了这个for循环并且进入了方法fixupMethodList进行了方法修复。我们就跟踪到这个方法去看看他的实现:

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循环的遍历去获取到了每一个方法的名字然后利用注册方法sel_registerNameNoLock进行了名字sel绑定注册最后利用set方法set进了method
第二步:进行了sort排序。根据注释我们知道他不会对small lists方法进行排序因为他不可变。
第三步:给非small lists方法列表打标记调用setFixedUp方法。

我们不妨来打印下,看看排序前和排序后的方法名称和地址。同时为了控制使我们自己的类的方法我们在上面的methodizeClass方法添加的打印里打上一个断点,只有来到这个断点的时候证明是我们自己的类ZYPerson的方法。这个时候我们再清空打印,并且在for循环打印的代码后面加上一个断点(此断点随意打在打印后的代码就可)为了避免系统后面的方法也打印出来。

让我们要观察的方法打印出来。如图6:

6.png

打印结果:如图7

7.png

到这里我们prepareMethodLists 方法的主要流程就走完了。

下面我们回到prepareMethodLists 方法入口methodizeClass方法里。在下一行代码打上断点查看走向。如图8

8.png

我们看到rwe在这个方法里为NULL。所以不会走下面的if方法attachLists。那么这个rwe是什么时候才会赋值的呢?我们放到文章后面来探讨。

至此我们可以理一遍流程:
第一步:从_read_images 方法进入 查找到关于类的方法readClass进行了类的名字和类的地址绑定;

第二步:走到另一部分代码 进行非懒加载类的加载处理 然后进入方法 realizeClassWithoutSwift进行rorw处理和父类元类isa走位绑定处理。然后进入方法处理方法methodizeClass;
第三步:在这个方法进行方法属性分类的处理,我们只是跟踪到了方法处理,进入到了方法预处理方法prepareMethodLists
第四步:对方法进行遍历然后调用方法修复方法fixupMethodList
第五步:fixupMethodList方法对方法进行名字sel绑定,并且排序处理。
这就是上面我们探索的内容。

总结:回归到我们之前为我们的类添加load方法,我们不加load方法是不会走我们read_images里的非懒加载类的处理流程。苹果这样做的目的就是为了节约内存,为了使得在开发过程中的类得到区分,把一些类定义为懒加载类(没有实现load方法的),在这些懒加载类没有用到之前都不会去加载和处理。这样就会使得内存得到大量的结余。启动时间更短运行更快。

疑问:既然上面我们谈到非懒加载类会走上面的处理流程,那懒加载类呢?懒加载类怎么处理的呢?

懒加载类加载处理流程

我们从上面非懒加载类的处理流程从read_images里的非懒加载类判断入口进入到realizeClassWithoutSwift方法。之后进行一些列的类处理。那我们猜测是否在其他的地方也有有入口进入realizeClassWithoutSwift这个方法呢?我们就在这个方法里之前添加的特殊打印的地方加一个断点。并且把ZYPersonload方法屏蔽掉.如果真的来了,那我们就利用bt命令来查看堆栈信息,追溯流程。

9.png
10.png

从堆栈流程我们发现是:lookUpImpOrForward->realizeAndInitializeIfNeeded_locked->initializeAndLeaveLocked->initializeAndMaybeRelock->realizeClassMaybeSwiftAndUnlock->realizeClassMaybeSwiftMaybeRelock->realizeClassWithoutSwift
从这个流程进入了我们上面分析的类加载处理流程。

那么是什么时机调用的上面的流程呢?

分析:我们从上面可以知道,当进入上面bt流程的时候ZYPerson只是进行了alloc,并没有去调用下面的方法。这时候就已经进入到这里了。

结论:所以我们可以确定懒加载类是在第一次消息发送的时候就会去加载这个相关的类。

总结:

下面我用两张图片来总结归纳懒加载和非懒加载类的加载处理过程:

类加载流程.png

分类(categofies)提前预告:

我们在上面探索类的加载流程的时候在methodizeClass方法里根据断点跟踪看到预处理方法列表后有判断rwe是否存在,那个时候我们发现是不存在的,那影响这个的因素是什么呢?而且我们在进入methodizeClass方法前看到他的注释是Attach categories这不得不让我们引发思考就是这里面的某些东西是否是跟分类有关的呢?下面我们来到main.m文件在main函数上方添加一个分类ZYPerson (ZY)给这个类设置两个属性和三个方法。然后我们利用 clang -rewrite-objc main.m -o main.cpp命令将main.m文件转成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,就是我们自己创建的那个ZYPerson(ZY)的分类。我们搜索下_category_t这个结构体

_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结构体的成员变量里看到了一个名字name,应该就是存储ZY这个字符;还有class,应该就是ZYPerson 类;然后就是实例方法类方法协议属性

在这里我们发现类方法居然是这些实例方法放一起的。不过我们想到分类是没有元类这一点也就能够理解了。因为没有元类所以他的类方法只能直接和实例方法放在一起了。

我们继续搜索下_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)的结构体中看到有一个名字记录的居然是ZYPerson?然后class记录的居然是个0?其实这里应该只是做一个占位作用,因为class不可能为0,而那个name也不应该是ZYPerson 而应该是ZY,唯一的解释就是在编译阶段还不能确定这个名字和类。所以只是占位。然后看到有实例方法类方法协议属性,和上面的结构体模型结构是一致的。

我们继续查看下关于分类的东西


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

从上面的两段代码我们可以发现,虽然分类的实例方法和类方法放在一起,但是他们在实现的时候是分开的。

协议_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"}}
};

在查找属性的时候我们并没有发现他的setter/getter方法。这也从侧面反映了我们经常提到的分类不能设置属性的说法,是因为它不会生成setter/getter方法,所以我们才用关联对象的方法来实现。

我们发现分类元类在编译阶段就已经做了一些处理,尤其在类方法、属性等特殊点上做了特殊的处理。所以我们下一篇文章就一起去探索下分类的一些实现原理。

至此,文章告一段落,原创码字不易,如能给您带来些许启发那也是给作者的极大鼓励。也盼望需要转载的朋友请标注出处,谢谢!

遇事不决,可问春风。站在巨人的肩膀上学习,如有疏忽或者错误的地方还请多多指教。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容