iOS底层原理之OC类的加载原理(中)

1,119 阅读20分钟

前言

前文iOS底层原理之OC类的加载原理(上)分析了_objc_init->map_images->_read_images的整个流程,最后定位到类的加载是在realizeClassWithoutSwift函数中完成的。本文将接着上文继续分析。

准备工作

一: realizeClassWithoutSwift函数

实现了+load方法的类在_read_images函数中会进入此函数,此函数对类进行了加载,并递归加载父类、元类,包括分配读写数据,返回类的真实类结构。

static Class realizeClassWithoutSwift(Class cls, Class previously)
{

    class_rw_t *rw;
    Class supercls;
    Class metacls;
    
    ...
    
    // 1. 生成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<class_rw_t>();
        rw->set_ro(ro);
        rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
        cls->setData(rw);
    }
    
    // 2. 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 为index 
    // 存入objc_indexed_classes中(其中记录了index-cls的关系)
    // 为isa是否纯指针做的处理
    cls->chooseClassArrayIndex();
    
    ...
    
    // 3. 关联父类与元类
    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);
    
    // 4. 调整ivars
    // 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);
    
    // 5. 同步flags标志位
    // 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;
    }
    
    // 6. 串联继承链
    // Connect this class to its superclass's subclass lists
    if (supercls) {
        addSubclass(supercls, cls);
    } else {
        addRootClass(cls);
    }
    
    // 7. Attach categories
    // Attach categories
    methodizeClass(cls, previously);
    
    return cls;
}
  1. 通过ro生成rw数据。
  2. cache初始化。
  3. 关联父类与元类。递归加载父类和元类,然后与当前类关联起来。
  4. 调整ivarsoffsets/layout
  5. 同步flags的标志位给rw
  6. 串联类的继承链。
  7. 分类附加到主类的逻辑在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 数据已经实现过了
        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空间
        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数据,此前类的原理探索篇章data()获取的是rw数据,这是因为此时rw还未赋值,只是从macho文件中名为__objc_classlist__objc_nlclslistsection中对应类的data中读取数据存入了data()中。具体可以在赋值前后验证cls->data()的地址。
  • 混乱的类:
    • 混乱的类就重新设置下rwro,并调用changeInfo函数修改下rwflags
  • 普通类:
    • 普通类就开辟rw空间,将ro存入到rw中(只是将地址给了rw,并不会完全复制一份,苹果对这块做了相应的优化)。
    • 设置rwflagsflags是一个uint32_t类型,第31位表示是否实例化完毕(RW_REALIZED),第19位表示是否实例化中(RW_REALIZING),第0位表示是否是元类(元类为1,非元类为0)。
    • rw存入data中。

⚠️因为元类和类的名称相同,元类和类都会进入realizeClassWithoutSwift函数加载,所以在调试代码部分加了判断isMeta,防止元类影响我们调试。

ro数据在llvm编译阶段就已经生成了。

class_ro_tllvm中的定义。

image.png

class_ro_t::read函数实现:

image.png

  • 根据class_ro_t结构体的成员变量计算出size
  • 根据传入的addr获取size大小的buffer
  • 通过buffer提取address里的数据。
  • class_ro_t结构体成员变量进行赋值。

class_ro_t::Read函数是被Read_class_row调用的。

image.png

lldb调试验证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没有显示任何数据,改为调用baseMethods()函数获取。
  • 然后调用get(0).big()获取方法的详细信息。

1.1.1: cls->data()获取ro数据探索

我们先来看看data()函数的定义:

struct objc_class中的data()函数定义。

class_rw_t *data() const {
    return bits.data();
}
  • 实际调用的是struct class_data_bits_t中的data()函数。
class_rw_t* data() const {
    return (class_rw_t *)(bits & FAST_DATA_MASK);
}
  • bits & FAST_DATA_MASK,然后强转成class_rw_t *返回。

bitsuintptr_t(unsigned long),也就是指针地址的10进制表现形式,FAST_DATA_MASK0x00007ffffffffff8ULbits & FAST_DATA_MASK结果也就是一个指针,然后强转成class_rw_t指针返回。

image.png

image.png

然后返回到realizeClassWithoutSwift函数后,又强转成了class_ro_t指针(class_rw_t包含class_ro_t,所以可以强转)。

image.png

那么既然返回的是指针,又是怎么变成class_ro_t结构体的呢?

class_ro_t结构体指针就指向这个结构体的首地址,读值、写值就是通过地址偏移的方式对结构体成员变量挨个读取、写入。如果指针类型与数据结构类型不匹配,就会解析失败。

1.1.2: 指针还原数据结构验证

下面通过Method(方法)指针还原objc_method结构体的方式来验证:

众所周知,我们可以通过runtime的相关API来获取方法,但是实际上返回的也是方法指针。

Method *me = class_getInstanceMethod([XJPerson class], @selector(loveEveryone));

Method在底层有两种定义:

// 非源码环境也可以看到
typedef struct objc_method *Method;

struct objc_method {
    SEL _Nonnull method_name                                 OBJC2_UNAVAILABLE;
    char * _Nullable method_types                            OBJC2_UNAVAILABLE;
    IMP _Nonnull method_imp                                  OBJC2_UNAVAILABLE;
};

// 源码环境才可以看到
typedef struct method_t *Method;

// 省略方法和静态成员变量
struct method_t {
    ...
    struct big {
        SEL name;
        const char *types;
        MethodListIMP imp;
    };
    ...
    struct small {
        RelativePointer<const void *> name;
        RelativePointer<const char *> types;
        RelativePointer<IMP> imp;
    };
    ...
}
  • 虽然objc_method结构体的成员变量注明OBJC2_UNAVAILABLE,但是却是苹果在非源码提供给我们的数据类型,也可以用来测试。
  • method_t结构体由于比较复杂,我们可以直接简化来进行测试。

image.png

  • 可以看到都顺利的由指针解析出了对应的结构体数据。

所以ro数据是从macho文件读取对应的地址指针后针对class_ro_t结构体解析得到。

method_tllvm中的定义:

image.png

method_t::read函数实现:

image.png

  • 计算出size
  • 根据传入的addr获取size大小的buffer
  • 通过buffer提取address里的数据。
  • method_t结构体成员变量进行赋值。

method_t::Read函数是被Describe调用的。

image.png

1.2: cache初始化

cache初始化为空缓存。

    cls->cache.initializeToEmptyOrPreoptimizedInDisguise();
inline void initializeToEmptyOrPreoptimizedInDisguise() { initializeToEmpty(); }

void cache_t::initializeToEmpty()
{
    _bucketsAndMaybeMask.store((uintptr_t)&_objc_empty_cache, std::memory_order_relaxed);
    _originalPreoptCache.store(nullptr, std::memory_order_relaxed);
}
struct objc_cache _objc_empty_cache =
{
    0,        // mask
    0,        // occupied
    { NULL// buckets
};
  • 最终调用到cache_t::initializeToEmpty函数将cache初始化为空缓存。

1.3: 关联父类与元类

    // 父类和元类的加载
    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环境变量配置来指定isa是否为纯指针。
    • OS_objectisa为纯指针。
    • 父类isa是纯指针,并且父类还有父类。那么本类也要是纯指针。rawIsaIsInherited(只用于控制打印语句)表示纯指针是继承的。
    • 递归设置子类isa为纯指针(父类isa为纯指针,子类isa也要为纯指针)。
  • 关联父类与元类。也就是继承链与isa指向链。

1.4: 调整ivarsoffsets/layout

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

    // 设置成员变量占用空间大小
    cls->setInstanceSize(ro->instanceSize);
  • 在有父类,并且非元类的情况下,调用reconcileInstanceVariables函数协调成员变量偏移/布局。
  • 调用setInstanceSize函数重新设置instanceSize(实例变量)大小,并且如果尚未设置cachefastInstanceSize,就设置它。

成员变量的解读将在下篇文章分析,本文重点是方法的加载。

1.5: ro同步flags标志位给rw

// 拷贝ro的flags到rw中  
// flags第2位 c++构造/析构函数 RO_HAS_CXX_STRUCTORS,
// flags第8位 只有c++析构函数 RO_HAS_CXX_DTOR_ONLY
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中(C++构造和析构函数flag是放在cache中)。
    • RO_HAS_CXX_STRUCTORSflags2位,C++构造/析构函数。
    • RO_HAS_CXX_DTOR_ONLYflags8位,只有C++析构函数。
  • 本类禁止关联对象或者父类禁止关联对象(父类禁止,子类也需禁止),则设置RW_FORBIDS_ASSOCIATED_OBJECTS标记。
    • RW_FORBIDS_ASSOCIATED_OBJECTSflags20位,禁止关联对象。

1.6: 串联类的继承链

if (supercls) {
    //关联子类
    addSubclass(supercls, cls);
} else {
    //设置根类 nextSiblingClass 为 _firstRealizedClass 根类是第一个被实例化的类。
    addRootClass(cls);
}
  • 本类有父类的情况下,将本类连接到其父类的子类列表。
    • 设置本类的相邻类。
    • 本类继承父类C++构造/析构函数的flag
    • 本类继承父类RW_NOPREOPT_CACHERW_NOPREOPT_SELSflag
    • 本类继承父类FAST_CACHE_REQUIRES_RAW_ISA/RW_REQUIRES_RAW_ISAflag
  • 本类无父类的情况下,将本类设置为新的根类。

1.6.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++`构造/析构函数的`flag`
        if (supercls->hasCxxCtor()) {
            subcls->setHasCxxCtor();
        }

        if (supercls->hasCxxDtor()) {
            subcls->setHasCxxDtor();
        }

        ...
        
        // 本类继承父类`RW_NOPREOPT_CACHE`和`RW_NOPREOPT_SELS`的`flag`
        if (!supercls->allowsPreoptCaches()) {
            subcls->setDisallowPreoptCachesRecursively(__func__);
        } else if (!supercls->allowsPreoptInlinedSels()) {
            subcls->setDisallowPreoptInlinedSelsRecursively(__func__);
        }

        // Special case: instancesRequireRawIsa does not propagate
        // from root class to root metaclass
        // 本类继承父类`FAST_CACHE_REQUIRES_RAW_ISA`/`RW_REQUIRES_RAW_ISA`的`flag`
        // 根元类不继承
        if (supercls->instancesRequireRawIsa()  &&  supercls->getSuperclass()) {
            subcls->setInstancesRequireRawIsaRecursively(true);
        }
    }
}
  • 设置本类的相邻类为父类的第一个子类(首次为nil)。
  • 设置本类为父类的第一个子类。
  • 本类继承父类C++构造/析构函数的flag
  • 本类继承父类RW_NOPREOPT_CACHERW_NOPREOPT_SELSflag(共享缓存和inlined sels)。
  • 本类继承父类FAST_CACHE_REQUIRES_RAW_ISA/RW_REQUIRES_RAW_ISAflagisa是否为纯指针)。根元类不继承。

1.6.2: addRootClass

static void addRootClass(Class cls)
{
    ...
    objc_debug_realized_class_generation_count++;
    // 本类的相邻类设置为第一个加载的类(nil)
    cls->data()->nextSiblingClass = _firstRealizedClass;
    // 第一个加载的类设置为自己
    _firstRealizedClass = cls;
}
  • 本类无父类的情况下,将本类的相邻类设置为nil,并将本类设置为第一个加载的类,即根类。

二: methodizeClass

realizeClassWithoutSwift函数最后调用了methodizeClass函数来对主类方法列表进行排序和加载分类。

    // Attach categories
    methodizeClass(cls, previously);
  • 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();
    // rwe这里通常获取的为NULL,因为此时还没有调用extAllocIfNeeded函数创建rwe
    auto rwe = rw->ext();

    ...
    
    // Install methods and properties that the class implements itself.
    // 安装类自己实现的方法和属性和协议
    // 方法、属性、协议列表通常不是在此处添加进rwe的,
    // 因为创建rwe时就添加了
    
    // 获取ro的方法列表
    method_list_t *list = ro->baseMethods();
    if (list) {
        // isBundleClass,判断类是否在一个不可加载的捆绑包中,绝不能由编译器设置
        // RO_FROM_BUNDLE,第29位
        // 方法列表排序(按sel地址)
        prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls), nullptr);
        // 如果有rwe,就将主类的方法列表添加到rwe的methods
        if (rwe) rwe->methods.attachLists(&list, 1);
    }
    
    // 获取ro的属性列表
    property_list_t *proplist = ro->baseProperties;
    if (rwe && proplist) {
        // 有rwe且proplist不为NULL
        // 就将主类的属性列表添加到rwe的properties
        rwe->properties.attachLists(&proplist, 1);
    }
    
    // 获取ro的协议列表
    protocol_list_t *protolist = ro->baseProtocols;
    if (rwe && protolist) {
        // 有rwe且协议列表不为NULL
        // 就将主类的协议列表添加到rwe的protocols
        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.
    // 分类附加到主类,previously == nil,走下面流程
    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);
    
    ...
}
  • rwe这里通常获取的为NULL,因为此时还没有调用extAllocIfNeeded函数创建rwe
  • 主类方法、属性、协议处理。
    • 主类的方法列表修正和排序(按sel地址排序)。
    • rwe才会执行下面流程,通常不会执行。
      • 主类的方法列表添加到rwemethods
      • 主类的属性列表添加到rweproperties
      • 主类的协议列表添加到rweprotocols
  • 如果是根元类,就添加initialize方法。
  • 分类的attachToClass处理。(此类没有分类的情况下不会执行)。

2.1: prepareMethodLists

// 主类方法排序传参
// cls, &list, 1, YES, isBundleClass(cls), nullptr
// 分类方法排序传参,分类mlists里面存的是method_list_t *类型
// cls, mlists, mcount, NO, fromBundle, __func__
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++) {
        method_list_t *mlist = addedLists[i];
        ASSERT(mlist);

        // Fixup selectors if necessary
        // 如果没有排序,就修正且排序
        if (!mlist->isFixedUp()) {
            // 修正并且排序
            fixupMethodList(mlist, methodsFromBundle, true/*sort*/);
        }
    }
    ……
}
  • isBundleClass,判断类是否在一个不可加载的捆绑包中,绝不能由编译器设置。RO_FROM_BUNDLE,第29位。
  • addedLists**类型。那么mlist就是主类方法列表的指针或某一个分类方法列表的指针。 image.png
  • 如果没有排序,就修正且排序ro->baseMethods()

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();
    }
}
  • 先对methodSEL进行了修正。sel_registerNameNoLock -> __sel_registerName -> search_builtins->_dyld_get_objc_selector。相当于以dyld的为准。
  • 接着以SEL的地址对methodList进行排序,这里就与慢速消息查找流程的二分查找对应上了。排序是在这里进行的。
  • 最后将方法列表标记为已修正且排序。

small lists情况下不进行排序,因为它们是不可变的。

SEL修正前后:

image.png

排序验证:

image.png

三: 分类探索

methodizeClass函数里面有针对rwe方法列表、属性列表和协议列表等等的相关处理,由于没有rwe数据,所以相关流程都不会执行。根据之前WWDC的介绍知道在有分类或者动态给类添加方法、属性、协议的情况下会有rwe数据,所以添加个分类来进行探索。

@interface XJPerson (CatA)

@property (nonatomic, copy) NSString *cata_Name;

@property (nonatomic, assign) int cata_age;

- (void)cata_instanceMethod1;

- (void)cata_instanceMethod2;

+ (void)cata_classMethod1;

@end

@implementation XJPerson (CatA)

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

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

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

@end

3.1: clang还原底层代码探索

为了方便研究分类的实现原理,先通过clang探索下底层的实现。

通过clang指令将.m文件文件编译成.cpp

clang -rewrite-objc main.m -o main.cpp

.cpp文件里搜索分类的名字CatA,在文件的最后面找到了如下的定义:

static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [1] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= {
    &_OBJC_$_CATEGORY_XJPerson_$_CatA,
};
  • _OBJC_$_CATEGORY_XJPerson_$_CatA_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;
};
  • 分类在底层也是结构体类型。
  • 成员变量name应该是分类的名字CatA
  • cls指向类。
  • 类只有一个methods(方法列表),但是分类却有instance_methodsclass_methods,这是因为分类没有元类(即没有分元类)。
  • 分类也有protocolsproperties

_OBJC_$_CATEGORY_XJPerson_$_CatA定义:

static struct _category_t _OBJC_$_CATEGORY_XJPerson_$_CatA __attribute__ ((used, section ("__DATA,__objc_const"))) = 
{
    "XJPerson",
    0, // &OBJC_CLASS_$_XJPerson,
    (const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_XJPerson_$_CatA,
    (const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_XJPerson_$_CatA,
    0,
    (const struct _prop_list_t *)&_OBJC_$_PROP_LIST_XJPerson_$_CatA,

};
  • 这里nameXJPerson而不是CatA,是因为静态编译的时候还不知道分类的名字,所以赋值了类的名字。
  • cls没有赋值,但是有注释,是因为此时还未关联,需要运行时进行关联。
  • 因为分类没有遵守协议,所以协议为空。

遵守NSObject协议后,重新编译查看:

image.png

image.png

  • 此时协议就有值了,正是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_XJPerson_$_CatA __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_prop_t),
    2,
    {{"cata_Name","T@\"NSString\",C,N"},
    {"cata_age","Ti,N"}}
};
  • 属性列表能找到分类声明的相应的属性。

继续查看方法列表:

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_XJPerson_$_CatA __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    2,
    {{(struct objc_selector *)"cata_instanceMethod1", "v16@0:8", (void *)_I_XJPerson_CatA_cata_instanceMethod1},
    {(struct objc_selector *)"cata_instanceMethod2", "v16@0:8", (void *)_I_XJPerson_CatA_cata_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_XJPerson_$_CatA __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    1,
    {{(struct objc_selector *)"cata_classMethod1", "v16@0:8", (void *)_C_XJPerson_CatA_cata_classMethod1}}
};
  • 并没有自动生成属性对应的setter & getter方法。所以分类属性的setter & getter方法只能通过关联对象处理。

3.2: category_t源码验证

objc源码搜索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;
    }
};
  • objc源码里的category_t结构与clang转换的.cpp文件里的_category_t结构基本相同,只是category_t多了一个_classProperties。根据注释此变量并不总是出现在磁盘上,所以暂且不研究它。

3.3:官方文档探索

Xcode里同时按住commandshift0打开官方文档,搜索Category,查看相关说明:

image.png

  • 文档里Categoryobjc_category的重命名。

objc源码里搜索objc_category结构体探索:

image.png

  • objc_categoryobjc 2.0已经不可用了。可见苹果文档分类相关内容多久没更新了,也有可能是苹果为了隐藏底层真实的实现故意为之。

3.4: 源码探索分类加载

经过上面的探索,大致了解了分类的本质:分类和类一样,本质也是一个结构体。那么下面就探索下分类是怎么加载的。

methodizeClass函数里分类附加到本类的核心函数是attachListsattachToClass,如果rwe存在,就会调用attachLists函数将本类方法、属性、协议添加到rwe中,但是通常此时rwe并未创建。所以我们先来梳理下分类的加载流程。

3.4.1: rwe创建时机分析

methodizeClass函数里rwe获取方式:

auto rwe = rw->ext();

ext函数实现:

struct class_rw_t {
    ...
    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,设置rwe,并返回地址指针
            return extAlloc(v.get<const class_ro_t *>(&ro_or_rw_ext));
        }
    }

    // rwe深拷贝
    class_rw_ext_t *deepCopy(const class_ro_t *ro) {
        return extAlloc(ro, true);
    }

    class_rw_ext_t *
    class_rw_t::extAlloc(const class_ro_t *ro, bool deepCopy)
    {
        runtimeLock.assertLocked();

        // 开辟内存
        auto rwe = objc::zalloc<class_rw_ext_t>();

        // 元类version为7,非元类为0
        rwe->version = (ro->flags & RO_META) ? 7 : 0;

        // 主类的方法,属性,协议是在此处添加进rwe中的
        // 即创建完rwe就添加
        // methodizeClass函数不会添加,因为那时通常还没有rwe

        // 获取主类方法列表
        method_list_t *list = ro->baseMethods();
        if (list) {
            // deepCopy默认为false,如果为true就复制一份
            // class_rw_ext_t *extAlloc(const class_ro_t *ro, bool deep = false);
            // extAlloc函数的声明可以说明
            if (deepCopy) list = list->duplicate();
            // 将主类方法列表添加到rwe的methods中
            // methodizeClass函数已经进行过修复并排序了
            rwe->methods.attachLists(&list, 1);
        }

        // See comments in objc_duplicateClass
        // property lists and protocol lists historically
        // have not been deep-copied
        //
        // This is probably wrong and ought to be fixed some day
        // 获取主类属性列表
        property_list_t *proplist = ro->baseProperties;
        if (proplist) {
            // 将主类属性列表添加到rwe的properties中
            rwe->properties.attachLists(&proplist, 1);
        }

        // 获取主类的协议列表
        protocol_list_t *protolist = ro->baseProtocols;
        if (protolist) {
            // 将主类协议列表添加到rwe的protocols中
            rwe->protocols.attachLists(&protolist, 1);
        }
        // 设置rwe和ro
        set_ro_or_rwe(rwe, ro);
        // 返回rwe
        return rwe;
    }
    ...
}
  • ext函数获取rwe
  • extAllocIfNeeded函数判断rwe是否存在,存在就返回,不存在就调用extAlloc函数进行rwe的创建。
  • extAlloc函数进行rwe的创建,并将主类的方法列表,属性列表,协议列表添加进rwe对应的列表中(不是在methodizeClass函数添加的,因为那时还没有rwe),然后设置rwero,并返回rwe

综上所述,只有调用了extAllocIfNeeded函数才会创建rwe,全局搜索发现调用extAllocIfNeeded函数的有以下函数(rwe创建情况):

  • attachCategories函数。
  • objc_class::demangledName函数(isRealized() ||  isFuture()的情况下)。
  • class_setVersion函数。
  • addMethods_finish函数。
  • class_addProtocol函数。
  • _class_addProperty函数。
  • objc_duplicateClass函数。

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

3.4.2: 分类加载流程初探

全局搜索发现attachCategories函数是在attachToClass函数和load_categories_nolock函数中调用的。

attachToClass函数调用了attachCategories函数: image.png

load_categories_nolock函数调用了attachCategories函数: image.png

3.4.2.1: 分类加载流程一

全局搜索attachToClass,发现只在methodizeClass函数中调用了。

// 二大节分析过了,此处只贴出加载分类相关代码
static void methodizeClass(Class cls, Class previously)
{   
    ... 
    
    // Attach categories.
    // previously传过来的是nil,不走if里面流程
    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);
                                             
    ...
}
  • methodizeClass函数前面分析过是被realizeClassWithoutSwift函数调用的。
  • previously这个判断条件在源码里都为nil,可能是备用参数,作为苹果内部调试使用的。

结合前面分析可以得出分类加载流程一:

  • map_images --> map_images_nolock --> _read_images --> realizeClassWithoutSwift --> methodizeClass --> attachToClass --> attachCategories --> attachLists

3.4.2.2: 分类加载流程二

全局搜索load_categories_nolock函数,发现是被loadAllCategories函数和_read_images函数调用的。前面分析过了分类的加载必须在load_images函数调用之后,所以就只需要接着探索loadAllCategories函数了。

static void loadAllCategories() {
    mutex_locker_t lock(runtimeLock);
    for (auto *hi = FirstHeader; hi != NULL; hi = hi->getNext()) {
        load_categories_nolock(hi);
    }
}

继续搜索loadAllCategories函数,发现是被load_images函数调用的。

static bool  didInitialAttachCategories = false;

void
load_images(const char *path __unused, const struct mach_header *mh)
{
    if (!didInitialAttachCategories && didCallDyldNotifyRegister) {
        // didInitialAttachCategories为true时才会走`_read_images`里面的`load_categories_nolock`,
        // 但是下一行代码就调用了loadAllCategories
        // 此处设置为true,以保证loadAllCategories只调用一次
        didInitialAttachCategories = true;
        // 加载分类
        loadAllCategories();
    }
    // Return without taking locks if there are no +load methods here.
    if (!hasLoadMethods((const headerType *)mh)) return;
    recursive_mutex_locker_t lock(loadMethodLock);
    // Discover load methods
    {
        mutex_locker_t lock2(runtimeLock);
        prepare_load_methods((const headerType *)mh);
    }
    // Call +load methods (without runtimeLock - re-entrant)
    call_load_methods();
}
  • didInitialAttachCategories默认是false,执行loadAllCategories函数前将didInitialAttachCategories设为true,以保证只调用一次loadAllCategories函数。
  • _objc_init函数调用_dyld_objc_notify_register(&map_images, load_images, unmap_image)注册完回调后didCallDyldNotifyRegister = true

经过分析可以得出分类加载流程二:

  • load_images --> loadAllCategories --> load_categories_nolock --> attachCategories --> attachLists

分类加载的详细流程将在下文继续分析。

总结

realizeClassWithoutSwift核心逻辑以思维导图的形式展示如下:

realizeClassWithoutSwiftl流程图.png