Objective-C 类的加载原理(中)

上篇文章分析了 _objc_init与read_images 的逻辑,最后定位到了类的初始化是在realizeClassWithoutSwift中的,这篇文章将继续分析。

一、 realizeClassWithoutSwift

realizeClassWithoutSwift中发现了对rorw等的一系列操作。在read_imags中要进入这个方法需要实现+ load方法。
核心逻辑精简后如下:

static Class realizeClassWithoutSwift(Class cls, Class previously)
{
    class_rw_t *rw;
    Class supercls;
    Class metacls;

……
1.生成rw数据逻辑
    //cache的初始化
    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
    //为32位设计的。objc_indexed_classes_count 记录类的数量 最终存入objc_indexed_classes中(其中记录了index-cls的关系)。为isa是否纯指针做的处理。
    cls->chooseClassArrayIndex();
2.关联元类与父类
    

3.调整ivars

4.同步flags标志位

5.关联子类与相邻类

    // Attach categories
    //分类处理
    methodizeClass(cls, previously);

    return cls;
}
  • 通过ro生成rw数据。
  • isa的判断处理,关联元类与父类。这里会递归父类和元类的realizeClassWithoutSwift,最后与cls关联。
  • 调整ivarsoffset
  • 同步flags的标志位给rw(实际上是从ro中存储到缓存中)。
  • 关联子类与相邻的类。
  • 分类的处理逻辑在methodizeClass中,将单独分析。

1.1、生成rw数据

auto ro = (const class_ro_t *)cls->data();
auto isMeta = ro->flags & RO_META;

//调试代码 开始
const char *mangledName = cls->nonlazyMangledName();
if (strcmp(mangledName, "HPObject") == 0 && !isMeta) {
    printf("%s %s\n",__func__,mangledName);
}
//调试代码 结束

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空间
    rw = objc::zalloc<class_rw_t>();
    //将ro数据存入rw中。这也是为什么从data()->ro()的原因。
    rw->set_ro(ro);
    //flags标志位设置 uint32_t 1<<31 1<<19 1<<0 (元类为1,非元类为0)
    rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
    //设置rw数据,这个时候data()拿出来的就是rw数据了。
    cls->setData(rw);
}
  • 首先通过cls->data()获取ro数据,在之前的class结构探索的时候data()获取的是rw数据。这是因为rw还没有赋值,从macho中读取__objc_classlist就存在了data()中了。具体可以在赋值前后验证cls->data()的地址。
  • rw开辟空间,将ro链接到rwro()中。(这里只是将地址给了rw,并没有完全复制一份,苹果对这块做了对应的优化。具体可以参考OC 类探索(二) 或者观看WWDC2020-10163)
  • 设置rwflags是一个uint32_t类型,31位表示rw是否已经初始化完毕(RW_REALIZED),19位表示rw是否初始化中(RW_REALIZING),0位表示是否是元类(元类为1,非元类为0)。
  • 最后将rw存进data中。

⚠️这里有个细节是调试代码中判断了isMeta,因为元类和类的名称相同。否则元类也会进入这块逻辑。
ro数据是在llvm编译期就已经生成了。class_ro_tllvm中的结构:

image.png

read函数实现:
image.png

可以看到是直接进行的macho文件的操作,直接进行的赋值。调用方是Read_class_row
image.png

验证ro数据(类加载进内存就有了):

(lldb) p ro
(const class_ro_t *) $0 = 0x00000001000080a8
(lldb) p *$0
(const class_ro_t) $1 = {
  flags = 128
  instanceStart = 8
  instanceSize = 8
  reserved = 0
   = {
    ivarLayout = 0x0000000000000000
    nonMetaclass = nil
  }
  name = {
    std::__1::atomic<const char *> = "HPObject" {
      Value = 0x0000000100003f2e "HPObject"
    }
  }
  baseMethodList = 0x00000001000080f0
  baseProtocols = 0x0000000000000000
  ivars = 0x0000000000000000
  weakIvarLayout = 0x0000000000000000
  baseProperties = 0x0000000000000000
  _swiftMetadataInitializer_NEVER_USE = {}
}
(lldb) p $1.baseMethodList
(void *const) $2 = 0x00000001000080f0
(lldb) p *$2
(lldb) p $1.baseMethods()
(method_list_t *) $3 = 0x00000001000080f0
(lldb) p *$3
(method_list_t) $4 = {
  entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 24, count = 1)
}
(lldb) p $4.get(0).big()
(method_t::big) $5 = {
  name = "instanceMethod"
  types = 0x0000000100003f57 "v16@0:8"
  imp = 0x0000000100003ea0 (HPObjcTest`-[HPObject instanceMethod])
}
  • 通过打印ro地址定位到了baseMethodList
  • 打印baseMethodList并没有显示任何数据,可以通过baseMethods()方法或者将baseMethodList强转成method_list_t *来打印。
  • 最后可以通过get(0).big()获取到方法的详细信息。

验证data数据:

image.png

  • 刚开始存储的是ro数据,生成rw数据后就存储rw数据了,ro数据存储在rw数据中。

验证一般情况下rw的方法列表指向ro的方法列表:

image.png

1.2 cls关联元类与父类

    //父类和元类的实例化
    supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil);
    metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);

#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走位。
    cls->setSuperclass(supercls);
    cls->initClassIsa(metacls);
  • 递归实例化父类和元类。
  • 判断设置isa是否纯指针。
    • 元类isa是纯指针。
    • 类的isa是否纯指针取值flags第13位。
    • DisableNonpointerIsa也就是OBJC_DISABLE_NONPOINTER_ISA环境变量配置来配置是否纯指针。
    • OS_object是纯指针。
    • 父类是纯指针,并且父类还有父类。那么自己也要是纯指针。rawIsaIsInherited (只是控制打印)表示继承的是纯指针。
    • 递归设置isa为纯指针,子类也设置为纯指针。(父类为纯指针,子类也为纯指针)。
  • 关联父类与元类,也就是继承链与isa走位。

1.3 调整ivar offset

//调整ivar的offset,可能会重新创建`class_ro_t`来更新ivar
if (supercls  &&  !isMeta) reconcileInstanceVariables(cls, supercls, ro);

//设置成员变量占用空间大小
cls->setInstanceSize(ro->instanceSize);
  • 在有父类的情况下,并且非元类会进行ivar offset的调整,具体逻辑在reconcileInstanceVariables中。
  • 重新设置成员变量的大小。逻辑在setInstanceSize中,其中有对常量的修改。

对于成员变量的解读,将放在下一篇文章。这篇文章的重点是方法的加载。

1.4 rw同步ro标志位

//拷贝ro的flags到rw中  flags第2位 c++构造方法 RO_HAS_CXX_STRUCTORS,flags第8位 RO_HAS_CXX_DTOR_ONLY(c++析构方法)
if (ro->flags & RO_HAS_CXX_STRUCTORS) {
    cls->setHasCxxDtor();
    if (! (ro->flags & RO_HAS_CXX_DTOR_ONLY)) {
        cls->setHasCxxCtor();
    }
}

//是否禁止关联对象 第20位标记是否允许关联对象。
if ((ro->flags & RO_FORBIDS_ASSOCIATED_OBJECTS) ||
    (supercls && supercls->forbidsAssociatedObjects()))
{
    rw->flags |= RW_FORBIDS_ASSOCIATED_OBJECTS;
}
  • 拷贝roflagsrw中(其实是放在缓存中)。
  • RO_HAS_CXX_STRUCTORS flags2位标记c++构造方法。
  • RO_HAS_CXX_DTOR_ONLY flags8位标记c++析构方法。
  • 自己禁止关联对象或者父类禁止关联对象(父类禁止,子类肯定禁止),则同步RW_FORBIDS_ASSOCIATED_OBJECTS标记。flags20位标记是否禁止关联对象。

1.5 子类与根类的设置

if (supercls) {
    //关联子类
    addSubclass(supercls, cls);
} else {
    //设置根类 nextSiblingClass 为 _firstRealizedClass 根类是第一个被实例化的类。
    addRootClass(cls);
}
  • addSubclass目标是设置父类的子类。同时也设置了子类的相邻类以及c++构造和析构的标记。同时根据父类是否isa纯指针同步给子类。
  • addRootClass设置根类,在这个流程中NSObject的相邻类会被设置为nil_firstRealizedClass会被设置为NSObject

1.5.1 addSubclass

static void addSubclass(Class supercls, Class subcls)
{
……
    if (supercls  &&  subcls) {
……

        objc_debug_realized_class_generation_count++;
        //相邻类
        subcls->data()->nextSiblingClass = supercls->data()->firstSubclass;
        //第一个子类
        supercls->data()->firstSubclass = subcls;
        //同步父类的c++析构和构造
        if (supercls->hasCxxCtor()) {
            subcls->setHasCxxCtor();
        }

        if (supercls->hasCxxDtor()) {
            subcls->setHasCxxDtor();
        }
        
……
        //同步子类isa是否纯指针
        if (supercls->instancesRequireRawIsa()  &&  supercls->getSuperclass()) {
            subcls->setInstancesRequireRawIsaRecursively(true);
        }
    }
}
  • 设置子类的相邻类(nextSiblingClass)为父类的第一个子类(firstSubclass)。
  • 设置父类的第一个子类为cls
  • 同步设置父类的c++析构和构造函数的标记给子类。
  • 同步父类的isa是否纯指针给子类。

1.5.2 addRootClass

static void addRootClass(Class cls)
{
    runtimeLock.assertLocked();

    ASSERT(cls->isRealized());

    objc_debug_realized_class_generation_count++;
    //自己的相邻类设置为第一个初始化的类(nil)。第一个初始化的类设置为自己。
    cls->data()->nextSiblingClass = _firstRealizedClass;
    _firstRealizedClass = cls;
}
  • NSObject的相邻类会被设置为nil_firstRealizedClass会被设置为NSObject

但是经过断点调试发现这个方法会进入两次:


image.png

第一次符合预期。第二次是__NSAtom进入的。它的相邻类是NSObject_firstRealizedClass变成了__NSAtom。堆栈是在+ load方法后:

image.png

那么__NSAtom是什么呢?根据上面的逻辑__NSAtom的相邻类为NSObject,第一个初始化的类变成了__NSAtom。在objc源码中并没有搜到相关类,只有OBJC_TAG_NSAtom。在class-dumpCoreFoundationCoreFoundation源码中也没有开源这一部分)头文件中发下了如下声明:

@interface __NSAtom : _UKNOWN_SUPERCLASS_ {

    Class isa;

}
+(void)initialize;
@end

那就证明__NSAtom是属于CoreFoundation的。目前暂不清楚这个类的用法以及作用。
参考

二、methodizeClass

realizeClassWithoutSwift中最后调用了如下代码:

// Attach categories
methodizeClass(cls, previously);

根据注释可以看到应该是对分类的处理,参数cls没问题,previously是从_read_images中传过来的为nil

realizeClassWithoutSwift(cls, nil);

methodizeClass核心逻辑如下:

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

   ……
    //获取ro方法列表
    method_list_t *list = ro->baseMethods();
    if (list) {
        //RO_FROM_BUNDLE 29 位标记是否bundleclass
        prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls), nullptr);
        if (rwe) rwe->methods.attachLists(&list, 1);
    }
    
    //获取ro属性列表
    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.
    //是否根元类,根元类加了initialize方法。
    if (cls->isRootMetaclass()) {
        // root metaclass
        addMethod(cls, @selector(initialize), (IMP)&objc_noop_imp, "", NO);
    }
    ……
    分类的处理
    // Attach categories.
    objc::unattachedCategories.attachToClass(cls, cls,
                                             isMeta ? ATTACH_METACLASS : ATTACH_CLASS);
   ……
}
  • ro的方法列表修正和排序。
  • 方法的attachLists处理。(没有rwe的情况下不走)
  • 属性的attachLists处理。(没有rwe的情况下不走)
  • 协议的attachLists处理。(没有rwe的情况下不走)
  • 根元类添加initialize方法。
  • 分类的attachToClass处理。(最终不会走进attachCategories逻辑)

2.1 prepareMethodLists

//cls, &list, 1, YES, isBundleClass(cls), nullptr
static void 
prepareMethodLists(Class cls, method_list_t **addedLists, int addedCount,
                   bool baseMethods, bool methodsFromBundle, const char *why)
{
……
    // Add method lists to array.
    // Reallocate un-fixed method lists.
    // The new methods are PREPENDED to the method list array.
    //addedCount 为 1
    for (int i = 0; i < addedCount; i++) {
        //addedLists 也就是list。
        method_list_t *mlist = addedLists[i];
        ASSERT(mlist);

        // Fixup selectors if necessary
        //是否已经排序,没有则进行排序。对ro methodlist 排序
        if (!mlist->isFixedUp()) {
            //修正并且排序methodList
            fixupMethodList(mlist, methodsFromBundle, true/*sort*/);
        }
    }
……
}
  • RO_FROM_BUNDLE flags29位标记是否bundleclass
  • addedCount值为1addedLists**类型。那么mlist就是rolist
  • 如果没有排序则修正并且排序romethodLists

验证mlist

image.png

2.1.1 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) {
            //SEL转成name字符串
            const char *name = sel_cname(meth.name());
            //将name和地址设置进meth中
            printf("before setName:%s address:%p\n",name,meth.name());
            //设置SEL,SEL有可能在 __sel_registerName 最终调用了_dyld_get_objc_selector的值,相当于修正到dyld中。
            meth.setName(sel_registerNameNoLock(name, bundleCopy));
            printf("after setName:%s address:%p\n",name,meth.name());
        }
    }

    // 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.
    //排序,通过SEL的地址排序。samll lists 不可变,不排序。这里就与慢速消息查找的二分查找对应上了。在这里进行的排序。
    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();
    }
}
  • 先对methodname也就是SEL进行了修正。sel_registerNameNoLock -> __sel_registerName -> search_builtins->_dyld_get_objc_selector。相当于以dyld的为准。
  • 接着对methodList进行了排序(按照SEL的地址),这里就与慢速消息查找的二分查找对应上了。排序是在这里进行的。
  • 最后设置标志位。

small lists不可变不进行排序。

修正前后SEL验证:

image.png

发现run的地址发生了变化,修正为dyld提供的地址了。

添加log进行排序前后验证:

image.png

其它方法顺序没有发生变化,run发生了变化。根据以上验证可以得出以下结论:

  • 方法的顺序默认是按照编译时候的顺序排序的,一般情况下是有序的。(文件中方法的顺序)
  • 在进行dyld修正SEL地址后需要重新排序。

2.2 分类的探索

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

@interface HPObject (HP)

@property (nonatomic, copy) NSString *hp_name;
@property (nonatomic, assign) int hp_age;

- (void)hp_instanceMethod1;

- (void)hp_instanceMethod2;

+ (void)hp_classMethod1;

@end

@implementation HPObject (HP)

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

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

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

@end

2.2.1 clang还原底层代码

既然要研究分类的实现,不防先探索下底层的实现。
在转换后的cpp文件中有如下实现:

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

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

接着有以下代码:

static struct _category_t _OBJC_$_CATEGORY_HPObject_$_HP __attribute__ ((used, section ("__DATA,__objc_const"))) = 
{
    "HPObject",//名字
    0, // &OBJC_CLASS_$_HPObject,//cls
    (const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_HPObject_$_HP,//实例方法
    (const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_HPObject_$_HP,//类方法
    0,///协议
    (const struct _prop_list_t *)&_OBJC_$_PROP_LIST_HPObject_$_HP,//属性
};
  • 这里name给了HPObject而不是HP,因为静态编译的时候还不知道名字。只是做了赋值。
  • cls没有赋值,但是有注释。这个时候还没有关联,需要运行时关联。
  • 协议也没有赋值。

直接遵循NSObject协议再重新编译下:

image.png

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

这个时候协议就有值了,正是NSObject协议。

这个时候属性确实已经有了:

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_HPObject_$_HP __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_prop_t),
    2,
    {{"hp_name","T@\"NSString\",C,N"},
    {"hp_age","Ti,N"}}
};

但是并没有属性队形的set & get

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_HPObject_$_HP __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    2,
    {{(struct objc_selector *)"hp_instanceMethod1", "v16@0:8", (void *)_I_HPObject_HP_hp_instanceMethod1},
    {(struct objc_selector *)"hp_instanceMethod2", "v16@0:8", (void *)_I_HPObject_HP_hp_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_HPObject_$_HP __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    1,
    {{(struct objc_selector *)"hp_classMethod1", "v16@0:8", (void *)_C_HPObject_HP_hp_classMethod1}}
};

所以只能通过关联对象处理。

2.2.2 category_t 源码验证

struct category_t {
    const char *name;
    classref_t cls;
    WrappedPtr<method_list_t, PtrauthStrip> instanceMethods;
    WrappedPtr<method_list_t, PtrauthStrip> 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);
    
    protocol_list_t *protocolsForMeta(bool isMeta) {
        if (isMeta) return nullptr;
        else return protocols;
    }
};
  • 在源码中category_tclang转换的c++代码结构不同。
  • 不仅类型不同,并且还有一个_classProperties

2.2.3 文档探索

Xcode文档中搜索Category得到以下内容:

//An opaque type that represents a category.
typedef struct objc_category *Category;

在源码中搜索objc_category发现OBJC2_UNAVAILABLE

struct objc_category {
    char * _Nonnull category_name                            OBJC2_UNAVAILABLE;
    char * _Nonnull class_name                               OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable instance_methods     OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable class_methods        OBJC2_UNAVAILABLE;
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
}                                                            OBJC2_UNAVAILABLE;

2.2.4 分类加载源码探究

通过上面的分析,大概了解了分类的结构。分类本身是一个结构体,那么它是怎么加载的呢?
通过类的加载源码的分析核心逻辑在attachListsattachToClass中。控制条件是rwe
rwe来源于rw->ext()

auto rwe = rw->ext();

ext实现如下:

class_rw_ext_t *ext() const {
    return get_ro_or_rwe().dyn_cast<class_rw_ext_t *>(&ro_or_rw_ext);
}

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

extAllocIfNeeded中进行了rwe的创建。extAllocIfNeeded的调用分为以下情况(rwe创建情况):

  • attachCategories
  • demangledNameisRealized()或者isFuture()
  • class_setVersion类的版本设置。
  • addMethods_finish
  • class_addProtocol
  • _class_addProperty
  • objc_duplicateClass

可以看到除了attachCategories,其它要么是对类进行动态处理要么是修复类的时候创建rwe。这与WWDC上的介绍就相吻合了。那么显然核心逻辑就在attachCategories了。

attachCategories的调用逻辑在attachToClassload_categories_nolock中。

  • attachToClass是在methodizeClass中调用的
  • load_categories_nolock是在_read_images(这里不会调用)与loadAllCategories中。
  • loadAllCategories是在load_images的时候加载。

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

  1. methodizeClass -> attachToClass -> attachCategories
  2. load_images -> loadAllCategories -> load_categories_nolock -> attachCategories

分类加载的详细实现将在下篇文章分析。

三、总结

  • realizeClassWithoutSwift
    • 通过ro生成rw数据。这里rw只是关联到了ro
    • isa判断处理,关联元类与父类。
      • 元类isa是纯指针。
      • isa是递归设置的,父类为纯指针,子类也为纯指针。
    • 调整ivarsoffset
    • 同步flags的标志位给rw(实际上是从ro中存储到缓存中)。
    • 关联子类(firstSubclass)与相邻的类(nextSiblingClass)。
    • methodizeClass(主要是对方法列表进行排序 & 加载分类 & rwe 的处理)
      • prepareMethodLists
        • fixupMethodList 修正并且排序方法列表(ro的)
          • sel_registerNameNoLock最终执行_dyld_get_objc_selectorSEL地址修复为dyld提供的。
          • SortBySELAddress对方法列表进行排序。
      • attachToClass分类的加载。
  • rwe
    • rwe是在extAllocIfNeeded中创建的。
      • 加载分类。
      • 动态修改类(addMethods_finishclass_addProtocol_class_addProperty)。
    • 修复类(demangledNameclass_setVersionobjc_duplicateClass)。
  • 分类的加载有两条路径:
    • methodizeClass -> attachToClass -> attachCategories
    • load_images -> loadAllCategories -> load_categories_nolock -> attachCategories
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念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

推荐阅读更多精彩内容