iOS底层探索之类的加载(二): realizeClassWithoutSwift分析

1. 回顾

在上篇博文中,已经从dyld_objc_init再到read_images整个流程串联起来了,最后定位到了类的初始化是在realizeClassWithoutSwift中,本篇博文将深入分析类的加载,请搬好板凳坐下认真往下看。

iOS底层探索之类的加载(一):read_images分析

2.realizeClassWithoutSwift

read_images流程中,会对类进行一些修复工作,同时会将类的名称与类进行关联,插入对照表中,并更新到内存中的类表。
rwro的处理我们还不得而知。那么编译生成的MachO文件中类的相关信息,是何时插入到内存对应的cls中的呢?

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

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

read_images源码中的部分注释可以知道,这是针对非懒加载的类进行初始化操作。那么什么叫做非懒加载的类呢?就是实现了+load方法

  • 非懒加载的类实现了+load方法
  • 通过nlclslist函数获取非懒加载类列表。
  • 对类进行递归处理,完成非懒加载类的初始化工作。
  • addClassTableEntry把类添加到内存表中。
  • realizeClassWithoutSwift初始化类。

2.1 realizeClassWithoutSwift源码

/***********************************************************************
* 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?
    const char * className = "JPStudent";
    if (strcmp(class_getName(cls), className) == 0)
    {
         printf("hello JPStudent...");
    }

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

2.2 ro、rw的处理

machO中获取的数据地址,根据class_ro_t格式进行强转,同时初始化rw的空间,并复制一份ro的数据放入rw中。

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();
    rw->set_ro(ro);
    rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
    cls->setData(rw);
}

  • ro属于clean memory,在编辑时即确定的内存空间,只读,加载后不会发生改变的内存空间,包括类名称、方法、协议和实例变量的信息;
  • rw的数据空间属于dirty memoryrw是运行时的结构,可读可写,由于其动态性,可以往类中添加属性、方法、协议。在运行时会发生变更的内存。

具体可以去看看wwdc2020里面做了很详细的说明和分析。

2.3 类的处理

父类和元类的处理

    // 递归,加载父类、元类的实现
    supercls = realizeClassWithoutSwift(remapClass(cls->superclass), nil);
    metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);

对于是否支持NONPOINTER_ISA的类进行处理,指针优化是指Isa的末尾位是1
对于元类以及特殊情况下的场景的一些类,无需开启指针优化的类,使用Raw Isa,Isa的末尾位是0

#if SUPPORT_NONPOINTER_ISA
    if (isMeta) {
        //元类isa是纯指针。
        cls->setInstancesRequireRawIsa();
    } else {
        //isa是否纯指针, flags中第13位
        bool instancesRequireRawIsa = cls->instancesRequireRawIsa();
        bool rawIsaIsInherited = false;
        static bool hackedDispatch = false;
        //这就是环境变量中配置的 OBJC_DISABLE_NONPOINTER_ISA
        if (DisableNonpointerIsa) {
            // Non-pointer isa disabled by environment or app SDK version
            //配置环境变量为YES后,isa是一个纯指针。
            instancesRequireRawIsa = true;
        }
        //OS_object类时纯指针
        else if (!hackedDispatch  &&  0 == strcmp(ro->getName(), "OS_object"))
        {
            // hack for libdispatch et al - isa also acts as vtable pointer
            hackedDispatch = true;
            instancesRequireRawIsa = true;
        }
        //父类是纯指针,并且父类还有父类。那么自己也要是纯指针。rawIsaIsInherited 表示继承的是纯指针
        else if (supercls  &&  supercls->getSuperclass()  &&
                 supercls->instancesRequireRawIsa())
        {

            instancesRequireRawIsa = true;
            rawIsaIsInherited = true;
        }
        //递归设置isa为纯指针,子类也设置为纯指针。(父类为纯指针,子类也为纯指针)。rawIsaIsInherited只是控制打印。
        if (instancesRequireRawIsa) {
            cls->setInstancesRequireRawIsaRecursively(rawIsaIsInherited);
        }
    }
// SUPPORT_NONPOINTER_ISA
#endif
  • 递归实例化父类和元类。

  • 判断设置isa是否纯指针。

  • 元类isa是纯指针。

  • 类的isa是否纯指针取值flags第13位。

  • 父类是纯指针,并且父类还有父类。那么自己也要是纯指针。rawIsaIsInherited (只是控制打印)表示继承的是纯指针。

  • 递归设置isa为纯指针,子类也设置为纯指针。(父类为纯指针,子类也为纯指针)。

关联父类与元类


    //关联父类与元类。也就是继承链与isa走位。
    cls->setSuperclass(supercls);
    cls->initClassIsa(metacls);

2.3 代码调式

添加如下代码,定位调式

    const char * className = "JPStudent";
    if (strcmp(class_getName(cls), className) == 0)
    {
         printf("hello JPStudent...");
    }

设置断点,分别判断在ro初始化前、后,进行lldb调式打印ro的数据结构的变化。

  • ro 赋值前打印:
hello JPStudent...(lldb) p cls
(Class) $3 = 0x00000001000084e0
(lldb) p cls
(Class) $4 = JPStudent
(lldb) p ro
(const class_ro_t *) $5 = 0x00007ffeefbff190
(lldb) p *$5
(const class_ro_t) $6 = {
  flags = 4022333872
  instanceStart = 32766
  instanceSize = 0
  reserved = 0
   = {
    ivarLayout = 0x0000000100008508 "\xe0\x84"
    nonMetaclass = JPStudent
  }
  name = {
    std::__1::atomic = "\xe0\x84" {
      Value = 0x0000000100008508 "\xe0\x84"
    }
  }
  baseMethodList = 0x00007ffeefbff1e0
  baseProtocols = 0x00000001003269a0
  ivars = 0x00000001000084e0
  weakIvarLayout = 0x0000000100008508 "\xe0\x84"
  baseProperties = 0x000000010036d080
  _swiftMetadataInitializer_NEVER_USE = {}
}
  • ro 赋值后打印:
(lldb) p ro
(const class_ro_t *) $9 = 0x00000001000081b8
(lldb) p *$9
(const class_ro_t) $10 = {
  flags = 0
  instanceStart = 8
  instanceSize = 24
  reserved = 0
   = {
    ivarLayout = 0x0000000000000000
    nonMetaclass = nil
  }
  name = {
    std::__1::atomic = "JPStudent" {
      Value = 0x0000000100003d2d "JPStudent"
    }
  }
  baseMethodList = 0x0000000100008200
  baseProtocols = nil
  ivars = 0x0000000100008340
  weakIvarLayout = 0x0000000000000000
  baseProperties = 0x0000000100008388
  _swiftMetadataInitializer_NEVER_USE = {}
}
(lldb) p $10.baseMethodList
(void *const) $11 = 0x0000000100008200
(lldb) p *$11
(lldb) 

通过调式打印,并没有打印出方法列表,但是我JPStudent是有方法的,说明此时,只是有了ro的数据结构,只是个空的结构的地址,selimp还没有绑定起来。

realizeClassWithoutSwift中最后调用了如下代码,根据注释可以看到应该是对分类的处理,参数cls没问题,previously是从_read_images中传过来的为nil

// Attach categories
methodizeClass(cls, previously);

那么现在就对methodizeClass分析

3. methodizeClass分析

3.1 methodizeClass源码

/***********************************************************************
* 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();

    // Methodizing for the first time
    if (PrintConnecting) {
        _objc_inform("CLASS: methodizing class '%s' %s", 
                     cls->nameForLogging(), isMeta ? "(meta)" : "");
    }

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

    property_list_t *proplist = ro->baseProperties;
    if (rwe && proplist) {
        rwe->properties.attachLists(&proplist, 1);
    }

    protocol_list_t *protolist = ro->baseProtocols;
    if (rwe && protolist) {
        rwe->protocols.attachLists(&protolist, 1);
    }

    // Root classes get bonus method implementations if they don't have 
    // them already. These apply before category replacements.
    if (cls->isRootMetaclass()) {
        // root metaclass
        addMethod(cls, @selector(initialize), (IMP)&objc_noop_imp, "", NO);
    }

    // Attach categories.
    if (previously) {
        if (isMeta) {
            objc::unattachedCategories.attachToClass(cls, previously,
                                                     ATTACH_METACLASS);
        } else {
            // When a class relocates, categories with class methods
            // may be registered on the class itself rather than on
            // the metaclass. Tell attachToClass to look for those.
            objc::unattachedCategories.attachToClass(cls, previously,
                                                     ATTACH_CLASS_AND_METACLASS);
        }
    }
    objc::unattachedCategories.attachToClass(cls, cls,
                                             isMeta ? ATTACH_METACLASS : ATTACH_CLASS);

#if DEBUG
    // Debug: sanity-check all SELs; log method list contents
    for (const auto& meth : rw->methods()) {
        if (PrintConnecting) {
            _objc_inform("METHOD %c[%s %s]", isMeta ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(meth.name()));
        }
        ASSERT(sel_registerName(sel_getName(meth.name())) == meth.name());
    }
#endif
}
  • ro 、rw、rew初始化,元类判断标记
  • 获取方法列表
  • 获取属性列表
  • 获取协议列表
  • 是否根元类,根元类加了initialize方法
  • 分类的处理

methodizeClass方法进行断点调试,发现rwe为空,说明此时还没有对类进行相关的扩展操作,所以rwe还没有被创建初始化。那么此时我们对方法、属性、协议的添加操作也是无效的。

3.2 prepareMethodLists

method_list_t处理时调用prepareMethodLists了方法,核心代码如下:

// 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*/);
        }
    }

  • addedCount值为1addedLists**类型,就是指针的指针类型。那么mlist就是rolist

  • 如果没有排序则修正,再排序romethodLists

  • fixupMethodList

methodname也就是SEL进行了修正,也就与慢速消息查找的二分查找对应上了。

  • 排序验证

通过以下代码对 排序前 和排序后 进行方法的打印

for (auto& meth : *mlist) {
         const char *name = sel_cname(meth.name());
    printf("排序后:%s---%p\n",name,meth.name());
    }

奇怪的是排序前和排序后的顺序是一样的,我猜测有可能是顺序已经排序好了,无需要再排序,或者是添加的时候就已经是排好了。

4.分类的探索

prepareMethodLists执行完成后是没有rwe数据,所以后续的attachLists相关操作都不会执行。根据之前WWDC的介绍rwe在有分类的情况下会出现,那么就去建立一个分类看看。

4.1 分类底层结构

通过clang 查看分类结构

    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;
};
  • 分类也是一个结构体类型。
  • name的名字应该是分类名称。
  • cls指向类。
  • 在类中只有一个methods,在分类中有了instance_methodsclass_methods。因为分类没有元类(也就是没有分元类)。
  • 分类中是有properties的。

通过xcode帮助文档也可以查看

再去objc源码里面搜索objc_category验证一下

还真有哦!所以也验证了分类的底层是结构体!

4.2 分类源码探索

通过对methodizeClass源码的分析,分类的处理核心逻辑在attachListsattachToClass中。rwe的赋值是来源于rw->ext()代码如下:

auto rwe = rw->ext();
  • ext() 代码如下:
class_rw_ext_t *ext() const {
    return get_ro_or_rwe().dyn_cast(&ro_or_rw_ext);
}

class_rw_ext_t *extAllocIfNeeded() {
    //获取rwe
    auto v = get_ro_or_rwe();
    if (fastpath(v.is())) {
        return v.get(&ro_or_rw_ext);
    } else {
        //创建rwe
        return extAlloc(v.get(&ro_or_rw_ext));
    }
}

extAllocIfNeeded中进行了rwe的创建。通过在objc源码里面搜索extAllocIfNeeded的调用,发现了在attachCategories方法里面的调用比较符合。而attachCategories的调用逻辑在attachToClassload_categories_nolock中。

由此所以分类的加载就有了两条线路:

  • methodizeClass --> attachToClass --> attachCategories
  • load_images --> loadAllCategories --> load_categories_nolock --> attachCategories

5.总结

  • 懒加载类与⾮懒加载类: 指当前类是否实现load⽅法
  • 非懒加载类情况 map_images的时候 加载所有类数据_getObjc2NonlazyClassList --> readClass -- > realizeClassWithoutSwift --> methodizeClass
  • 懒加载类情况是数据加载推迟到第⼀次消息的时候,lookUpImpOrForward --> realizeClassMaybeSwiftMaybeRelock -- > realizeClassWithoutSwift --> methodizeClass

更多内容持续更新

喜欢就点个赞吧

觉得学习到了的,可以来一波,收藏+关注,评论 + 转发,以免你下次找不到我

欢迎大家留言交流,批评指正,互相学习,提升自我

你可能感兴趣的:(iOS底层探索之类的加载(二): realizeClassWithoutSwift分析)