今天看啥  ›  专栏  ›  Luminix

Runtime源代码解读2(类和对象)

Luminix  · 掘金  ·  · 2019-10-13 15:15
阅读 25

Runtime源代码解读2(类和对象)

2019-10-10

Runtime源代码解读(实现面向对象初探)中,从Cocoa框架中的runtime.h头文件公开的数据结构及 API 对 runtime 整体有一个大概的认知。从本文开始具体分析 Apple 开源的runtime源代码。本文介绍 runtime 如何通过C语言结构体实现类和对象,该部分应该是 runtime 的最核心代码。

注意:Github 搜索到的有一千多颗星的RetVal/objc-runtime工程,版本是750,最新公开的代码版本是756,后者在 ARC 支持、ivarLayout定义、Swift 兼容等方面有变动。

一、类的定义

Objective-C 中类的本质是objc_class结构体,其定义代码如下,包含以下成员:

  • isaobjc_class继承objc_object结构体,因此也包含isa指针,主要功能是指向对象的类型,新版本 runtime 中,isa指针并不一定是Class类型而是包含64 bit 数据的位图(bitmap),在 4.1 中详细介绍;
  • superclass:指向父类的指针,用于组织类的继承链;
  • cache:类使用哈希表数据结构缓存最近调用方法,以提高方法查找效率(TODO:后续独立文章中会介绍);
  • bitsclass_data_bits_t结构体类型,该结构体主要用于记录,保存类的数据的class_rw_t结构体的内存地址。通过date()方法访问bits的有效位域指向的内存空间,返回class_rw_t结构体;setData(class_rw_t *newData)用于设置bits的值;

注意:上述 bitmap 并不是图片的位图,而是指数据被视为简单的二进制数,将其中的一些或所有 bit 赋予特殊的含义,共同表示一种含义的 bit 或 bit 的集合称为位域。

struct objc_class : objc_object {
    // Class ISA;
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags

    class_rw_t *data() { 
        return bits.data();
    }
    void setData(class_rw_t *newData) {
        bits.setData(newData);
    }

    // 类的方法在文章第三部分详细介绍
    ...

};
复制代码

二、类的数据

类的数据主要保存在class_data_bits_t结构体中,其成员仅有一个bits指针。objc_classdata()方法用于获取bits成员的 4~47 位域(FAST_DATA_MASK)中保存的class_rw_t结构体地址。类的数据保存在class_rw_t结构体中,剩余的部分保存在ro指针指向的class_ro_t结构体中。

class_rw_tclass_ro_t结构体名中,rw是 read write 的缩写,ro是 read only 的缩写,可见class_ro_t的保存类的只读信息,这些信息在类完成注册后不可改变。以类的成员变量列表为例(成员变量列表保存在class_ro_t结构体中)。若应用类注册到内存后,使用类构建了若干实例,此时若添加成员变量必然需要对内存中的这些类重新分配内存,这个操作的花销是相当大的。若考虑再极端一些,为根类NSObject添加成员变量,则内存中基本所有 Objective-C 对象都需要重新分配内存,如此庞大的计算量在运行时是不可接受的。

#if !__LP64__
#define FAST_DATA_MASK        0xfffffffcUL
#elif 1
#define FAST_DATA_MASK          0x00007ffffffffff8UL
#endif

#if (!__LP64__  ||  TARGET_OS_WIN32  ||  \
     (TARGET_OS_SIMULATOR && !TARGET_OS_IOSMAC))
#   define SUPPORT_PACKED_ISA 0
#else
#   define SUPPORT_PACKED_ISA 1
#endif

#if !SUPPORT_INDEXED_ISA  &&  !SUPPORT_PACKED_ISA
#   define SUPPORT_NONPOINTER_ISA 0
#else
#   define SUPPORT_NONPOINTER_ISA 1
#endif

struct class_data_bits_t {
    uintptr_t bits;

private:
    bool getBit(uintptr_t bit)
    {
        return bits & bit;
    }

    ...

public:

    // 获取类的数据
    class_rw_t* data() {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }

    // 设置类的数据
    void setData(class_rw_t *newData)
    {
        // 仅在类注册、构建阶段才允许调用setData
        assert(!data()  ||  (newData->flags & (RW_REALIZING | RW_FUTURE)));
        uintptr_t newBits = (bits & ~FAST_DATA_MASK) | (uintptr_t)newData;
        atomic_thread_fence(memory_order_release);
        bits = newBits;
    }

    ...

// 是否支持非指针类型isa,在4.1介绍对象的isa指针时详细介绍
#if FAST_REQUIRES_RAW_ISA
    bool instancesRequireRawIsa() {
        return getBit(FAST_REQUIRES_RAW_ISA);
    }
    void setInstancesRequireRawIsa() {
        setBits(FAST_REQUIRES_RAW_ISA);
    }
#elif SUPPORT_NONPOINTER_ISA
    // 主流机型一般走到这个编译分支
    bool instancesRequireRawIsa() {
        return data()->flags & RW_REQUIRES_RAW_ISA;
    }
    void setInstancesRequireRawIsa() {
        data()->setFlags(RW_REQUIRES_RAW_ISA);
    }
#else
    bool instancesRequireRawIsa() {
        return true;
    }
    void setInstancesRequireRawIsa() {
        // nothing
    }
#endif

    ...

};
复制代码

1.1 class_rw_t 结构体

类的主要数据保存在bits中,bits以位图保存class_rw_t结构体,用于记录类的关键数据,如成员变量列表、方法列表、属性列表、协议列表等等,class_rw_t仅包含三个基本的位操作方法。class_rw_t包含以下成员:

  • flags:32位位图,标记类的状态;
  • version:标记类的类型,0表示类为非元类,7表示类为元类;
  • ro:保存类的只读数据,注册类后ro中的数据标记为只读,成员变量列表保存在ro中;
  • methods:方法列表,其类型method_array_t为二维数组容器(TODO:后续在独立文章介绍);
  • properties:属性列表,其类型property_array_t为二维数组容器(TODO:后续在独立文章介绍);
  • protocols:协议列表,其类型protocol_array_t为二维数组容器;
  • firstSubclass:类的首个子类,与nextSiblingClass记录所有类的继承链组织成的继承树;
  • nextSiblingClass:类的下一个兄弟类;
  • demangledName:类名,来自Swift的类会包含一些特别前缀,demangledName是处理后的类名;
  • index:标记类的对象的isa是否为index类型;
#if __ARM_ARCH_7K__ >= 2  ||  (__arm64__ && !__LP64__)
#   define SUPPORT_INDEXED_ISA 1
#else
#   define SUPPORT_INDEXED_ISA 0
#endif

struct class_rw_t {
    uint32_t flags;
    uint32_t version;

    const class_ro_t *ro;

    method_array_t methods;
    property_array_t properties;
    protocol_array_t protocols;

    Class firstSubclass;
    Class nextSiblingClass;

    char *demangledName;

#if SUPPORT_INDEXED_ISA
    uint32_t index;
#endif

    //设置set指定的位
    void setFlags(uint32_t set) 
    {
        OSAtomicOr32Barrier(set, &flags);
    }

    // 清空clear指定的位
    void clearFlags(uint32_t clear) 
    {
        OSAtomicXor32Barrier(clear, &flags);
    }

    // 设置set指定的位,清空clear指定的位
    void changeFlags(uint32_t set, uint32_t clear) 
    {
        assert((set & clear) == 0);

        uint32_t oldf, newf;
        do {
            oldf = flags;
            newf = (oldf | set) & ~clear;
        } while (!OSAtomicCompareAndSwap32Barrier(oldf, newf, (volatile int32_t *)&flags));
    }

};
复制代码

class_rw_tflags成员中比较重要的一些位域定义列举如下,均以RW_为前缀,这些位域在类注册后仍可读写。

/************* 类注册后可读写的flags位域 *************/
// 类是已经注册的类
#define RW_REALIZED           (1<<31)
// 类是尚未解析的future class
#define RW_FUTURE             (1<<30)
// 类是已经初始化的类
#define RW_INITIALIZED        (1<<29)
// 类是正在初始化的类
#define RW_INITIALIZING       (1<<28)
// class_rw_t->ro是class_ro_t的堆拷贝
// 此时类的class_rw_t->ro是可写入的,拷贝之前ro的内存区域锁死不可写入
#define RW_COPIED_RO          (1<<27)
// 类是正在构建而仍未注册的类
#define RW_CONSTRUCTING       (1<<26)
// 类是已经构建完成并注册的类
#define RW_CONSTRUCTED        (1<<25)
// 类是load方法已经调用过的类
#define RW_LOADED             (1<<23)
#if !SUPPORT_NONPOINTER_ISA
// 类是可能实例可能存在关联对象的类
// 默认编译选项下,无需定义该位,因为都可能有关联对象
#define RW_INSTANCES_HAVE_ASSOCIATED_OBJECTS (1<<22)
#endif
// 类是具有实例相关的GC layout的类
#define RW_HAS_INSTANCE_SPECIFIC_LAYOUT (1 << 21)
// 类是禁止使用关联对象的类
#define RW_FORBIDS_ASSOCIATED_OBJECTS       (1<<20)
// 类是正在注册,但是未注册完成的类
#define RW_REALIZING          (1<<19)
复制代码

1.1 class_ro_t 结构体

类完成注册后,类的实例占用的内存大小、成员变量列表、成员变量内存布局等重要信息需要固定下来,这些在类注册后需要标记为只读的数据保存在class_ro_t结构体中,class_rw_t结构体的ro成员为指向该结构体的指针。class_ro_t结构体包含以下主要成员:

  • flags:32位位图,标记类的状态。需要注意class_ro_tflags使用的位域和前面介绍的class_rw_tflags使用的位域是完全不同的;
  • instanceStart:类的成员变量,在实例的内存空间中的起始偏移量;
  • instanceSize:类的实例占用的内存空间大小;
  • ivarLayout:成员变量内存布局,标记实例占用的内存空间中哪些WORD保存了成员变量数据;
  • name:类名;
  • baseMethodList:基础方法列表,在类定义时指定的方法列表;
  • baseProtocols:协议列表;
  • ivars:成员变量列表;
  • weakIvarLayout:weak成员变量布局;
  • baseProperties:基础属性列表,在类定义时指定的属性列表;
struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;
#ifdef __LP64__
    uint32_t reserved;
#endif

    const uint8_t * ivarLayout;
    
    const char * name;
    method_list_t * baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;

    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;

    ...

    method_list_t *baseMethods() const {
        return baseMethodList;
    }

    class_ro_t *duplicate() const {
        if (flags & RO_HAS_SWIFT_INITIALIZER) {
            size_t size = sizeof(*this) + sizeof(_swiftMetadataInitializer_NEVER_USE[0]);
            class_ro_t *ro = (class_ro_t *)memdup(this, size);
            ro->_swiftMetadataInitializer_NEVER_USE[0] = this->_swiftMetadataInitializer_NEVER_USE[0];
            return ro;
        } else {
            size_t size = sizeof(*this);
            class_ro_t *ro = (class_ro_t *)memdup(this, size);
            return ro;
        }
    }
};
复制代码

class_ro_tflags成员中比较重要的一些位域定义列举如下,均以RO_为前缀,这些位域在类注册后标记为只读。

/************* 类注册后只读的flags位域 *************/
// 类是元类
#define RO_META               (1<<0)
// 类是根类
#define RO_ROOT               (1<<1)
// 类有CXX构造/析构函数
#define RO_HAS_CXX_STRUCTORS  (1<<2)
// 类有实现load方法
// #define RO_HAS_LOAD_METHOD    (1<<3)
// 隐藏类
#define RO_HIDDEN             (1<<4)
// class has attribute(objc_exception): OBJC_EHTYPE_$_ThisClass is non-weak
#define RO_EXCEPTION          (1<<5)
// class has ro field for Swift metadata initializer callback
#define RO_HAS_SWIFT_INITIALIZER (1<<6)
// 类使用ARC选项编译
#define RO_IS_ARC             (1<<7)
// 类有CXX析构函数,但没有CXX构造函数
#define RO_HAS_CXX_DTOR_ONLY  (1<<8)
// class is not ARC but has ARC-style weak ivar layout 
#define RO_HAS_WEAK_WITHOUT_ARC (1<<9)
// 类禁止使用关联对象
#define RO_FORBIDS_ASSOCIATED_OBJECTS (1<<10)

// class is in an unloadable bundle - must never be set by compiler
#define RO_FROM_BUNDLE        (1<<29)
// class is unrealized future class - must never be set by compiler
#define RO_FUTURE             (1<<30)
// class is realized - must never be set by compiler
#define RO_REALIZED           (1<<31)
复制代码

注意:实际上在类的构建阶段,有时会操作class_rw_t去置flags中一些RO_前缀的位域,但仅发生在重叠的29/30/31位域。

三、类的行为

本章介绍objc_class结构体中定义的方法。

3.1 类加载过程相关行为

调用 runtime API 动态创建类的过程,包括三个步骤:

  • 调用Class objc_allocateClassPair(...)构建类;
  • 添加必要的成员变量、方法等元素;
  • 调用void objc_registerClassPair(Class cls)注册类;

然而,runtime 从镜像(image)加载类的过程会更加精细,在加载类的不同阶段会被标记为不同的类型(还是objc_class结构体,只是flags不同),例如:future class(懒加载类)、remapped class(已重映射类)、realized class(已认识类)、allocated class(已分配内存类)、named class(已确定名称类)、loaded class(已加载类)、initialized class(已初始化类)等。接下来重点介绍 future class、remapped class 和 realized class。其中标记为 allocated class 和 named class 只是简单地将类添加到全局管理的哈希表中,因此仅穿插在 future class、remapped class 中介绍;loaded class、initialized class 分别为已执行load方法的类和已执行initialize()方法的类。

objc_class结构体中与类的加载过程相关的函数代码如下,基本在class_rw_tclass_ro_tflags中存在RW_RO_前缀的位域与之对应:

    // 查询是否正在初始化(initializing)
    bool isInitializing() {
        return getMeta()->data()->flags & RW_INITIALIZING;
    }

    // 标记为正在初始化(initializing)
    void setInitializing() {
        assert(!isMetaClass());
        ISA()->setInfo(RW_INITIALIZING);
    }

    // 是否已完成初始化(initializing)
    bool isInitialized() {
        return getMeta()->data()->flags & RW_INITIALIZED;
    }

    void setInitialized(){
        Class metacls;
        Class cls;

        assert(!isMetaClass());

        cls = (Class)this;
        metacls = cls->ISA();

        // 关于alloc/dealloc/Retain/Release等特殊方法的判断及处理
        ...

        metacls->changeInfo(RW_INITIALIZED, RW_INITIALIZING);
    }

    bool isLoadable() {
        assert(isRealized());
        return true;  // any class registered for +load is definitely loadable
    }

    // 获取load方法的IMP
    IMP 
    objc_class::getLoadMethod()
    {
        runtimeLock.assertLocked();

        const method_list_t *mlist;

        assert(isRealized());
        assert(ISA()->isRealized());
        assert(!isMetaClass());
        assert(ISA()->isMetaClass());

        // 在类的基础方法列表中查询load方法的IMP
        mlist = ISA()->data()->ro->baseMethods();
        if (mlist) {
            for (const auto& meth : *mlist) {
                const char *name = sel_cname(meth.name);
                if (0 == strcmp(name, "load")) {
                    return meth.imp;
                }
            }
        }

        return nil;
    }

    // runtime是否已认识类
    bool isRealized() {
        return data()->flags & RW_REALIZED;
    }

    // 是否future class
    bool isFuture() { 
        return data()->flags & RW_FUTURE;
    }
复制代码

3.1.1 future class

objc_classisFuture()函数,用于判断类是否为 future class。本节通过代码一步步探讨 future class 的概念,future class 对理解类的加载过程有重要作用。

3.1.1.1 future class 生成

首先看 future class 是如何生成的addFutureNamedClass(const char *name, Class cls)函数用于将传入的cls参数,配置为类名为name的 future class,包含以下操作:

  • 分配cls所需的class_rw_tclass_ro_t的内存空间;
  • cls的类名置为name
  • class_rw_tRO_FUTURE位置为1,RO_FUTURE等于RW_FUTURE
  • name为关键字,将cls添加到一个全局的哈希表futureNamedClasses
static NXMapTable *future_named_class_map = nil;
static NXMapTable *futureNamedClasses()
{
    runtimeLock.assertLocked();
    
    if (future_named_class_map) return future_named_class_map;

    // future_named_class_map is big enough for CF’s classes and a few others
    future_named_class_map = 
        NXCreateMapTable(NXStrValueMapPrototype, 32);

    return future_named_class_map;
}

static void addFutureNamedClass(const char *name, Class cls)
{
    void *old;

    class_rw_t *rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1);
    class_ro_t *ro = (class_ro_t *)calloc(sizeof(class_ro_t), 1);
    ro->name = strdupIfMutable(name);
    rw->ro = ro;
    cls->setData(rw);
    cls->data()->flags = RO_FUTURE; 

    old = NXMapKeyCopyingInsert(futureNamedClasses(), name, cls);
    assert(!old);
}
复制代码

追踪调用addFutureClass(...)的代码,最终追溯到Class objc_getFutureClass(const char *name),该函数并没有在 runtime 源代码中被调用到。而用于从namedFutureClasses哈希表中获取 future class 的popFutureClass(...)函数是有间接通过readClass(...)函数被广泛调用。因此,构建 future class 的逻辑大多隐藏在 runtime 的内部实现中未公布,只有使用 future class 的逻辑是开源的

Class objc_getFutureClass(const char *name)
{
    Class cls;

    cls = look_up_class(name, YES, NO);
    if (cls) {
        if (PrintFuture) {
            _objc_inform("FUTURE: found %p already in use for %s", 
                         (void*)cls, name);
        }

        return cls;
    }
    
    // 若查找不到名为name的类则构建future class
    return _objc_allocateFutureClass(name);  
}

Class _objc_allocateFutureClass(const char *name)
{
    mutex_locker_t lock(runtimeLock);

    Class cls;
    NXMapTable *map = futureNamedClasses();

    if ((cls = (Class)NXMapGet(map, name))) {
        // 存在名为name的 future class
        return cls;
    }

    // 分配用于保存objc_class的内存空间
    cls = _calloc_class(sizeof(objc_class));  

    // 构建名为name的future class并全局记录到 futureNamedClasses 哈希表
    addFutureNamedClass(name, cls);  

    return cls;
}
复制代码
3.1.1.2 future class 应用

addFutureClass(...)操作明显是全局记录 future class 的过程,接下来追溯 何时用到 future classstatic Class popFutureNamedClass(const char *name)用于从futureNamedClasses哈希表中弹出类名为name的 future class,这是获取全局记录的 future class 的唯一入口。

static Class popFutureNamedClass(const char *name)
{
    runtimeLock.assertLocked();

    Class cls = nil;

    if (future_named_class_map) {
        cls = (Class)NXMapKeyFreeingRemove(future_named_class_map, name);
        if (cls && NXCountMapTable(future_named_class_map) == 0) {
            NXFreeMapTable(future_named_class_map);
            future_named_class_map = nil;
        }
    }

    return cls;
}
复制代码

popFutureNamedClassClass readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)函数中有被调用到,后者用于读取cls中的类数据,关键处理逻辑表述如下:

  • futureNamedClasses哈希表中存在cls->mangledName()类名的 future class,则将cls重映射(remapping)到新的类newCls(具体重映射过程在 3.1.2 中详细讨论),然后将newCls标记为 remapped class,以cls为关键字添加到全局记录的remappedClasses()哈希表中;
  • cls标记为 named class,以cls->mangledName()类名为关键字添加到全局记录的gdb_objc_realized_classes哈希表中,表示 runtime 开始可以通过类名查找类(注意元类不需要添加);
  • cls及其元类标记为 allocated class,并将两者均添加到全局记录的allocatedClasses哈希表中(无需关键字),表示已为类分配固定内存空间;

注意:传入readClass(...)cls参数是Class类型,而函数返回结果也是Class,为什么读取类信息是“从类中读取类信息”这样怪异的过程呢?其实是因为cls参数来源于 runtime 未开源的 从镜像(image)中读取类的过程,该过程输出的objc_class存在特殊之处:要么输出 future class,要么输出普通类但是其bits指向的是class_ro_t结构体而非class_rw_t,之所以如此是因为从镜像读取的是编译时决议的静态数据,本来就应该保存在class_ro_t结构体中。

Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)
{
    const char *mangledName = cls->mangledName();
    
    //类的继承链上,存在既不是根类(RO_ROOT位为0)又没有超类的类,则为missingWeakSuperclass
    //注意:这是唯一的向remappedClasses中添加nil值的入口
    if (missingWeakSuperclass(cls)) {
        addRemappedClass(cls, nil);
        cls->superclass = nil;
        return nil;
    }
    
    // 兼容旧版本libobjc的配置,可忽略
    cls->fixupBackwardDeployingStableSwift();

    Class replacing = nil;
    if (Class newCls = popFutureNamedClass(mangledName)) {
        // 已经全局记录该类名的 future class
        // 构建newCls并将cls的内容拷贝到其中,保存future class的rw中的数据
        // 以cls为关键字将构建的newCls添加到全局记录的remappedClasses哈希表中

        class_rw_t *rw = newCls->data();
        const class_ro_t *old_ro = rw->ro;
        memcpy(newCls, cls, sizeof(objc_class));
        rw->ro = (class_ro_t *)newCls->data();
        newCls->setData(rw);
        freeIfMutable((char *)old_ro->name);
        free((void *)old_ro);
        
        addRemappedClass(cls, newCls);
        
        replacing = cls;
        cls = newCls;
    }
    
    if (headerIsPreoptimized  &&  !replacing) {
        // 已存在该类名的named class
        assert(getClassExceptSomeSwift(mangledName));
    } else {
        // 将类添加到 named classes
        addNamedClass(cls, mangledName, replacing);

        // 将类添加到 allocated classes
        addClassTableEntry(cls);
    }

    // 设置RO_FROM_BUNDLE位
    if (headerIsBundle) {
        cls->data()->flags |= RO_FROM_BUNDLE;
        cls->ISA()->data()->flags |= RO_FROM_BUNDLE;
    }
    
    return cls;
}
复制代码

从上文readClass(...)代码if (Class newCls = popFutureNamedClass(mangledName))分支内free((void *)old_ro)语句,得出在cls映射到newCls过程中,完全丢弃了 future class 的ro数据。最后,结合以上所有代码,可以归纳以下结论:

  • Future class 类的有效数据实际上仅有:类名和rwrw中的数据作用也非常少,仅使用flagsRO_FUTURE(实际上就是RW_FUTURE)标记类是 future class;
  • Future class 的作用是为指定类名的类,提前分配好内存空间,调用readClass(...)函数读取类时,才正式写入类的数据。 Future class 是用于支持类的懒加载机制;

3.1.2 remapped class

在上文 3.1.1 有提到类的重映射,重映射的类被标记为 remapped class,并以映射前的类为关键字,添加到全局的remappedClass哈希表中。回顾Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)函数中,类的重映射代码如下,关于处理过程的详细描述已注释到代码中:

    // 1. 若该类名已被标记为future class,则弹出该类名对应的future class 赋值给newCls
    if (Class newCls = popFutureNamedClass(mangledName)) {
        // 2. rw记录future class的rw
        class_rw_t *rw = newCls->data();
        // 3. future class的ro记为old_ro,后面释放其占用的内存空间并丢弃
        const class_ro_t *old_ro = rw->ro;
        // 4. 将cls中的数据拷贝到newCls,主要是要沿用cls的isa、superclass和cache数据
        memcpy(newCls, cls, sizeof(objc_class));
        // 5. rw记录cls的ro
        rw->ro = (class_ro_t *)newCls->data();
        // 6. 沿用future class的rw、cls的ro
        newCls->setData(rw);
        // 7. 释放future class的ro占用的空间
        freeIfMutable((char *)old_ro->name);
        free((void *)old_ro);
        
        // 8. 将newCls以cls为关键字添加到remappedClasses哈希表中
        addRemappedClass(cls, newCls);
        
        replacing = cls;
        cls = newCls;
    }
复制代码

综合上面代码的详细注释,可知cls重映射到newCls后,newCls的数据保留了cls中的superclasscache成员,但是bits中指向class_rw_t结构体地址的位域(FAST_DATA_MASK)指向了新的class_rw_t结构体。该结构体的ro指针指向cls->data()所指向的内存空间中保存的class_ro_t结构体,其他数据则是直接沿用 从namedFutureClasses哈希表中弹出的 future class 的class_rw_t结构体(通过future class 的data()方法返回)中数据。

注意:虽然objc_classdata()方法声明为返回class_rw_t *,但是究其本质,它只是返回了objc_classbits成员的FAST_DATA_MASK标记的位域中保存的内存地址,该内存地址实际上可以保存任何类型的数据。在Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)函数中,传入的cls所指向的objc_class结构体有其特殊之处:clsbits成员的FAST_DATA_MASK位域,指向的内存空间保存的是class_ro_t结构体,并不是通常的class_rw_t

上述只是对 future class 的重映射过程,通用的类重映射调用static class remapClass(Class cls),注意当传入的cls类不在remappedClasses哈希表中时,直接返回cls本身;static void remapClassRef(Class *clsref)可对传入的Class* clsref重映射(改变*clsref的值),返回时clsref将 指向*clsref重映射后的类。类的重映射相关代码如下:

// 获取remappedClasses,保存已重映射的所有类的全局哈希表
static NXMapTable *remappedClasses(bool create)
{
    // 静态的全局哈希表,没有找到remove接口,只会无限扩张
    static NXMapTable *remapped_class_map = nil;

    runtimeLock.assertLocked();

    if (remapped_class_map) return remapped_class_map;
    if (!create) return nil;

    // remapped_class_map is big enough to hold CF’s classes and a few others
    INIT_ONCE_PTR(remapped_class_map, 
                  NXCreateMapTable(NXPtrValueMapPrototype, 32), 
                  NXFreeMapTable(v));

    return remapped_class_map;
}

// 将oldcls重映射得到的newcls,以oldcls为关键字插入到remappedClasses哈希表中
// 注意:从代码透露出来的信息是,remappedClasses中只保存 future class 重映射的类
static void addRemappedClass(Class oldcls, Class newcls)
{
    runtimeLock.assertLocked();

    if (PrintFuture) {
        _objc_inform("FUTURE: using %p instead of %p for %s", 
                     (void*)newcls, (void*)oldcls, oldcls->nameForLogging());
    }

    void *old;
    old = NXMapInsert(remappedClasses(YES), oldcls, newcls);
    assert(!old);
}

// 获取cls的重映射类
// 注意:当remappedClasses为空或哈希表中不存在`cls`关键字,是返回`cls`本身,否则返回`cls`重映射后的类
static Class remapClass(Class cls)
{
    runtimeLock.assertLocked();

    Class c2;

    if (!cls) return nil;

    NXMapTable *map = remappedClasses(NO);
    if (!map  ||  NXMapMember(map, cls, (void**)&c2) == NX_MAPNOTAKEY) {
        return cls;
    } else {
        return c2;
    }
}

// 对Class的指针的重映射,返回时传入的clsref将 指向*clsref重映射后的类
static void remapClassRef(Class *clsref)
{
    runtimeLock.assertLocked();

    Class newcls = remapClass(*clsref);    
    if (*clsref != newcls) *clsref = newcls;
}
复制代码

最后归纳出以下结论:

  • Future class 重映射返回新的类,保存在remappedClasses全局哈希表中;
  • 普通类重映射返回类本身;
  • 重映射的真正的目的是支持类的懒加载,懒加载类暂存为 future class 只记录类名及 future class 属性,在调用readClass才正式载入类数据。

3.1.3 realized class

调用readClass(...)读取类数据只是载入了类的class_ro_t静态数据,因此仍需要进一步配置objc_classclass_rw_t结构体的数据。这个过程为 class realizing,姑且称之为认识类。具体包括:

  • 配置class_rw_tRW_REALIZEDRW_REALIZING位;
  • 根据class_ro_tRO_META位的值,配置class_rw_tversion
  • 因为静态载入的父类、元类有可能被重映射,因此要保证类的父类、元类完成class realizing;
  • 配置class_rw_tsuperclass
  • 初始化objc_classisa指针;
  • 配置ivarLayoutinstanceSizeinstanceStart。该步骤非常重要,新版本 runtime 支持 non-fragile instance variables,类的instanceStartinstanceSize会根据父类的instanceSize动态调整,且需要按 WORD 对齐(TODO:后续在独立的文章中详细介绍);
  • 配置class_rw_tRO_HAS_CXX_STRUCTORSRO_HAS_CXX_DTOR_ONLYRW_FORBIDS_ASSOCIATED_OBJECTS
  • 添加子类/根类;
  • 从类的分类(category)中载入方法列表(TODO:后续在独立的文章中详细介绍);

实现 class realizing 的代码主要在static Class realizeClassWithoutSwift(Class cls)函数中,只需要知道其大致过程即可。具体代码及注释如下:

static Class realizeClassWithoutSwift(Class cls)
{
    runtimeLock.assertLocked();

    const class_ro_t *ro;
    class_rw_t *rw;
    Class supercls;
    Class metacls;
    bool isMeta;

    if (!cls) return nil;
    if (cls->isRealized()) return cls;
    assert(cls == remapClass(cls));  // 传入的类必须不存在于remappedClasses全局哈希表中

    ro = (const class_ro_t *)cls->data();
    if (ro->flags & RO_FUTURE) {
        // 若为future class,则cls的rw指向class_rw_t结构体,ro指向class_ro_t结构体,维持原状
        rw = cls->data();
        ro = cls->data()->ro;
        cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
    } else {
        // 普通类,则需要为rw分配内存,并将ro指针指向 传入的cls->data()所指向的内存空间
        rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1);
        rw->ro = ro;
        rw->flags = RW_REALIZED|RW_REALIZING;
        cls->setData(rw);
    }

    isMeta = ro->flags & RO_META;

    rw->version = isMeta ? 7 : 0;  // old runtime went up to 6

    // 忽略
    cls->chooseClassArrayIndex();

    // 父类realizing
    supercls = realizeClassWithoutSwift(remapClass(cls->superclass));
    // 元类realizing
    metacls = realizeClassWithoutSwift(remapClass(cls->ISA()));
 
#if SUPPORT_NONPOINTER_ISA
    // 配置RW_REQUIRES_RAW_ISA位。可忽略。
    bool instancesRequireRawIsa = cls->instancesRequireRawIsa();
    bool rawIsaIsInherited = false;
    static bool hackedDispatch = false;

    if (DisableNonpointerIsa) {
        instancesRequireRawIsa = true;
    }
    else if (!hackedDispatch  &&  !(ro->flags & RO_META)  &&  
             0 == strcmp(ro->name, "OS_object")) 
    {
        hackedDispatch = true;
        instancesRequireRawIsa = true;
    }
    else if (supercls  &&  supercls->superclass  &&  
             supercls->instancesRequireRawIsa()) 
    {
        instancesRequireRawIsa = true;
        rawIsaIsInherited = true;
    }
    
    // 配置RW_REQUIRES_RAW_ISA位
    if (instancesRequireRawIsa) {
        cls->setInstancesRequireRawIsa(rawIsaIsInherited);
    }
#endif

    // 由于存在class remapping的可能性,因此需要更新父类及元类
    cls->superclass = supercls;
    cls->initClassIsa(metacls);

    // 调整ivarLayout
    if (supercls  &&  !isMeta) reconcileInstanceVariables(cls, supercls, ro);

    // 调整instanceSize
    cls->setInstanceSize(ro->instanceSize);

    // 忽略
    if (ro->flags & RO_HAS_CXX_STRUCTORS) {
        cls->setHasCxxDtor();
        if (! (ro->flags & RO_HAS_CXX_DTOR_ONLY)) {
            cls->setHasCxxCtor();
        }
    }
    
    // 忽略
    if ((ro->flags & RO_FORBIDS_ASSOCIATED_OBJECTS) ||
        (supercls && supercls->forbidsAssociatedObjects()))
    {
        rw->flags |= RW_FORBIDS_ASSOCIATED_OBJECTS;
    }

    // 添加子类/根类
    if (supercls) {
        addSubclass(supercls, cls);
    } else {
        addRootClass(cls);
    }

    // 从Category中载入方法列表
    methodizeClass(cls);

    return cls;
}
复制代码

最后总结,截止至完成class realizing类的加载过程大致如下图所示。其中future class列是懒加载类(future class)的流程,经过了“添加懒加载类->加载懒加载类信息->懒加载类重映射->认识懒加载类”四步;normal class列是普通的非懒加载类的加载流程,只经过“加载类信息->认识类”两个步骤。

截止至完成class realizing类的加载过程.jpg

类完成 class realizing 后,还需要将类的分类(category)中的方法添加到类的方法列表中,然后执行类及分类中的load()方法,最后在程序运行过程中第一次调用类的方法时(实现逻辑在IMP lookUpImpOrForward(...)函数中)触发isInitialized()检查,若未初始化,则需要先执行类的initialize()方法。至此,类正式加载完成。

注意:最后的 class initializing 严格意义上应该不属于类的加载过程,可以将其归为独立的类初始化阶段。类的加载在load()方法执行后就算是完成了。

3.2 基本状态相关行为

objc_class结构体中类的基本状态查询的函数代码如下。注意Class getMeta()获取元类时:对于元类,getMeta()返回的结果与ISA()返回的结果不相同,对于非元类,两者则是相同的。

    bool isARC() {
        return data()->ro->flags & RO_IS_ARC;
    }

    bool isMetaClass() {
        assert(this);
        assert(isRealized());
        return data()->ro->flags & RO_META;
    }

    bool isMetaClassMaybeUnrealized() {
        return bits.safe_ro()->flags & RO_META;
    }

    Class getMeta() {
        if (isMetaClass()) return (Class)this;
        else return this->ISA();
    }

    bool isRootClass() {
        return superclass == nil;
    }
    bool isRootMetaclass() {
        return ISA() == (Class)this;
    }

    const char *mangledName() { 
        assert(this);

        if (isRealized()  ||  isFuture()) {
            return data()->ro->name;
        } else {
            return ((const class_ro_t *)data())->name;
        }
    }
    
    const char *demangledName();
    const char *nameForLogging();
复制代码

3.3 内存分配相关行为

根据类的信息构建对象时,需要根据类的继承链上的所有成员变量的内存布局为成员变量数据分配内存空间,分配内存空间的大小固定的,并按 WORD 对齐,调用size_t class_getInstanceSize(Class cls)实际是调用了objc_class结构体的uint32_t alignedInstanceSize()函数。

成员变量在实例内存空间中偏移量同样也是固定的,同样也是按 WORD 对齐。实例的第一个成员变量内存空间的在实例空间中的偏移量,实际是通过调用objc_class结构体的uint32_t alignedInstanceStart()函数获取。

objc_class结构体中涉及内存分配的函数代码如下:

    // 类的实例的成员变量起始地址可能不按WORD对齐
    uint32_t unalignedInstanceStart() {
        assert(isRealized());
        return data()->ro->instanceStart;
    }

    // 配置类的实例的成员变量起始地址按WORD对齐
    uint32_t alignedInstanceStart() {
        return word_align(unalignedInstanceStart());
    }

    // 类的实例大小可能因为ivar的alignment值而不按WORD对齐
    uint32_t unalignedInstanceSize() {
        assert(isRealized());
        return data()->ro->instanceSize;
    }

    // 配置类的实例大小按WORD对齐
    uint32_t alignedInstanceSize() {
        return word_align(unalignedInstanceSize());
    }

    // 获取类的实例大小
    size_t instanceSize(size_t extraBytes) {
        size_t size = alignedInstanceSize() + extraBytes;
        // CF requires all objects be at least 16 bytes. (TODO:不懂为啥)
        if (size < 16) size = 16;
        return size;
    }

    // 配置类的实例大小
    void setInstanceSize(uint32_t newSize) {
        assert(isRealized());
        if (newSize != data()->ro->instanceSize) {
            assert(data()->flags & RW_COPIED_RO);
            *const_cast<uint32_t *>(&data()->ro->instanceSize) = newSize;
        }
        bits.setFastInstanceSize(newSize);
    }

复制代码

四、对象

对象的数据结构是objc_object结构体。objc_object仅包含一个isa_t类型的isa指针,和<objc/runtime>定义的objc_object有所不同,后者的isa指针是Class(指向objc_class结构体)。这是因为新版本 runtime 支持非指针类型isa结构,非指针类型isa不再是指向Class的指针而是64位二进制位域,仅使用其中一部分位域保存对象的类的地址,其他位赋予特殊意义主要用于协助对象内存管理。

objc_object包含的方法主要有以下几类:

  • isa操作相关,isa指向对象类型,在控制对象构建、对象成员变量访问、对象消息响应,对象内存管理方面有十分关键的作用,将在 4.1 中详细介绍isa
  • 关联对象(associated object)相关;
  • 对象弱引用相关,对象释放后需要通知弱引用自动置nil,因此对象需要知晓所有弱引用的地址;
  • 引用计数相关,支持对象的引用计数(reference count)管理;
  • dealloc相关,对象析构相关,主要是释放对关联对象的引用;
  • side table 相关,side table 是 runtime 管理对象内存的核心数据结构,包含对象内存引用计数信息、对象的弱引用信息等关键数据(TODO:后续在独立文章中介绍);
  • 支持非指针类型isa相关;

对象的定义代码如下:

struct objc_object {
private:
    isa_t isa;

public:

    // 获取对象类型,建立在对象不是tagged pointer的假设上
    Class ISA();

    // 获取对象类型,对象可以是tagged pointer
    Class getIsa();

    // 初始化isa
    void initIsa(Class cls /*nonpointer=false*/);
    void initClassIsa(Class cls /*nonpointer=maybe*/);
    void initProtocolIsa(Class cls /*nonpointer=maybe*/);
    void initInstanceIsa(Class cls, bool hasCxxDtor);

    // 设置isa指向新的类型
    Class changeIsa(Class newCls);

    // 对象isa是否为非指针类型
    bool hasNonpointerIsa();

    // TaggedPointer相关,忽略
    bool isTaggedPointer();
    bool isBasicTaggedPointer();
    bool isExtTaggedPointer();

    // 对象是否是类
    bool isClass();

    // 对象关联对象相关
    bool hasAssociatedObjects();
    void setHasAssociatedObjects();

    // 对象弱引用相关
    bool isWeaklyReferenced();
    void setWeaklyReferenced_nolock();

    //对象是否包含 .cxx 构造/析构函数
    bool hasCxxDtor();

    // 引用计数相关
    id retain();
    void release();
    id autorelease();

    // 引用计数相关的实现
    id rootRetain();
    bool rootRelease();
    id rootAutorelease();
    bool rootTryRetain();
    bool rootReleaseShouldDealloc();
    uintptr_t rootRetainCount();

    // dealloc的实现
    bool rootIsDeallocating();
    void clearDeallocating();
    void rootDealloc();

private:
    void initIsa(Class newCls, bool nonpointer, bool hasCxxDtor);

    id rootAutorelease2();
    bool overrelease_error();

#if SUPPORT_NONPOINTER_ISA
    // 支持非指针类型isa
    id rootRetain(bool tryRetain, bool handleOverflow);
    bool rootRelease(bool performDealloc, bool handleUnderflow);
    id rootRetain_overflow(bool tryRetain);
    bool rootRelease_underflow(bool performDealloc);

    void clearDeallocating_slow();

    void sidetable_lock();
    void sidetable_unlock();

    void sidetable_moveExtraRC_nolock(size_t extra_rc, bool isDeallocating, bool weaklyReferenced);
    bool sidetable_addExtraRC_nolock(size_t delta_rc);
    size_t sidetable_subExtraRC_nolock(size_t delta_rc);
    size_t sidetable_getExtraRC_nolock();
#endif

    // Side-table 相关操作
    bool sidetable_isDeallocating();
    void sidetable_clearDeallocating();

    bool sidetable_isWeaklyReferenced();
    void sidetable_setWeaklyReferenced_nolock();

    id sidetable_retain();
    id sidetable_retain_slow(SideTable& table);

    uintptr_t sidetable_release(bool performDealloc = true);
    uintptr_t sidetable_release_slow(SideTable& table, bool performDealloc = true);

    bool sidetable_tryRetain();

    uintptr_t sidetable_retainCount();
#if DEBUG
    bool sidetable_present();
#endif
};
复制代码

Tagged Pointer:对于一个对象引用(指针),一般情况下该引用的值为对象的内存地址,而tagged pointer则直接在地址中写入对象的类和数据。

4.1 对象的 isa

objc_objectisa主要用于标记对象的类型。新版本 runtime 的isa支持两种形式:指针类型、非指针类型。前者简单指向对象的类。后者为64位二进制位域,当然其中也包括对象的类的地址,其他位域都有其特殊含义。为支持两种形式,runtime 使用isa_t联合体保存对象的isa

注意:Union 联合体的成员之间共享内存空间。以isa_t为例,cls成员和bits成员虽然不同,但是两者的值实际在任何时候都是一致的。例如,isa.class = [NSString class]指定了cls指向NSString类的内存地址,此时查看isa.bits会发现其值为NSString类的内存地址;反之,isa.bits = 0xFF,则isa.class的值也变为255

4.1.1 isa_t 联合体

isa_t联合体有两个成员Class clsuintptr_t bits,两者共享8个字节的内存空间(64位机)。以下为isa_t的源代码,删除了其中 x86_64 及其他架构下的代码。

union isa_t 
{
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    Class cls;
    uintptr_t bits;

// 需要编译选项支持非指针类型isa
#if SUPPORT_NONPOINTER_ISA

# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL
#   define ISA_MAGIC_MASK  0x000003f000000001ULL
#   define ISA_MAGIC_VALUE 0x000001a000000001ULL
    struct {
        uintptr_t indexed           : 1;
        uintptr_t has_assoc         : 1;
        uintptr_t has_cxx_dtor      : 1;
        uintptr_t shiftcls          : 33; // MACH_VM_MAX_ADDRESS 0x1000000000
        uintptr_t magic             : 6;
        uintptr_t weakly_referenced : 1;
        uintptr_t deallocating      : 1;
        uintptr_t has_sidetable_rc  : 1;
        uintptr_t extra_rc          : 19;
#       define RC_ONE   (1ULL<<45)
#       define RC_HALF  (1ULL<<18)
    };
# endif

// SUPPORT_NONPOINTER_ISA
#endif
};
复制代码

下面是 arm64 架构下isa_tbits的位域分布图示,左高位右低位。下面对各个位域的解析中有多次提及 side table(TODO:后续独立文章介绍),该结构用于对内存中的所有 Objective-C 对象进行统一的内存管理,其中最重要的是对象内存计数管理、对象弱指针管理。

  • indexed:洋红区域右起第1位。0表示isa为指针类型,存储类的地址;1表示isa为非指针类型。之所以使用最低位区分isa类型,是因为当isaClass时,其本质是指向objc_class结构体首地址的指针,由于objc_class必定按WORD对齐,即地址必定是8的整数倍,因此指针类型的isa的末尾3位必定全为0
  • has_assoc:洋红区域右起第2位。标记对象是否存在关联对象;
  • has_cxx_dtor:洋红区域右起第3位。标记对象是否存在cxx语系的析构函数。使用指针类型isa的对象,该标记保存在 side table 中;
  • shiftcls:红色区域共33位。保存类的虚拟内存地址,标记对象的类型(核心数据);
  • magic:黄色区域共6位。用于非指针类型的isa校验,arm64架构下这6位为固定值0x1a
  • weakly_referenced:青色区域右起第1位。标记对象是否被弱引用。使用指针类型isa的对象,该标记保存在 side table 中;
  • deallocating:青色区域右起第2位。标记对象是否已执行析构。使用指针类型isa的对象,该标记保存在 side table 中;
  • has_sidetable_rc:青色区域右起第3位。标记是否联合 side table 保存该对象的引用计数;
  • extra_rc:绿色区域共19位。记录对象引用计数,在has_sidetable_rc1时,需要联合 side table 才能获取对象的确切引用计数;

isa的各个位域.jpg

注意:MSB是Most Significant Bit指最高有效位,extra_rc需要处理上溢出情况因此为MSB,LSB是Least Significant Bit,indexed位用来判断isa指针的类型因此为LSB。

4.1.2 对象的 isa 相关操作

4.1.2.1 isa 的构建

对象构建时,需要直接或间接调用objc_objectinitIsa(Class cls, bool nonpointer, bool hasCxxDtor)构建isa,其他initIsa方法均在内部调用了该方法。其中cls参数表示对象的类,nonpointer表示是否构建非指针类型isahasCxxDtor表示对象是否存在cxx语系析构函数。

inline void 
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) 
{ 
    assert(!isTaggedPointer()); 
    
    if (!nonpointer) {
        // 构建非指针类型isa
        isa.cls = cls;
    } else {
        // 构建非指针类型isa
        assert(!DisableNonpointerIsa);
        assert(!cls->instancesRequireRawIsa());

        isa_t newisa(0);

#if SUPPORT_INDEXED_ISA
        assert(cls->classArrayIndex() > 0);
        newisa.bits = ISA_INDEX_MAGIC_VALUE;
        newisa.has_cxx_dtor = hasCxxDtor;

        newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
        // 当前主流机型一般会运行到这个逻辑分支
        newisa.bits = ISA_MAGIC_VALUE;  // magic设置为0xA1,index设置为1
        newisa.has_cxx_dtor = hasCxxDtor;

        // shiftcls位域保存对象的类的地址,注意最低3位不需要保存,因为必定是全0
        newisa.shiftcls = (uintptr_t)cls >> 3;
#endif

        // 指向新的newisa
        isa = newisa;
    }
}
复制代码
4.1.2.2 isa 的应用

非指针类型isa实际是将 side table 中部分内存管理数据(包括部分内存引用计数、是否包含关联对象标记、是否被弱引用标记、是否已析构标记)转移到isa中,从而减少objc_object中内存管理相关操作的 side table 查询数量。objc_object中关于状态查询的方法,大多涉及到isa的位操作。方法数量有点多不一一列举,本节只以弱引用相关查询为例。

  • isWeaklyReferenced()用于查询对象是否被弱引用。当对象isa为非指针类型时,直接返回isa.weakly_referenced,否则需要调用sidetable_isWeaklyReferenced ()从 side table 中查询结果;

  • setWeaklyReferenced_nolock()用于设置对象是否被弱引用。当对象isa为非指针类型时,仅需将weakly_referenced位置为1,否则需要调用sidetable_setWeaklyReferenced_nolock()从 side table 中查询结果并写入。

inline bool
objc_object::isWeaklyReferenced()
{
    assert(!isTaggedPointer());
    if (isa.nonpointer) return isa.weakly_referenced;
    else return sidetable_isWeaklyReferenced();
}


inline void
objc_object::setWeaklyReferenced_nolock()
{
// 源代码设置weakly_referenced过程比较繁杂
 retry:
    isa_t oldisa = LoadExclusive(&isa.bits);
    isa_t newisa = oldisa;
    if (slowpath(!newisa.nonpointer)) {
        ClearExclusive(&isa.bits);
        sidetable_setWeaklyReferenced_nolock();
        return;
    }
    if (newisa.weakly_referenced) {
        ClearExclusive(&isa.bits);
        return;
    }
    newisa.weakly_referenced = true;
    if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry;
}
复制代码

4.2 对象的构建

对象的构建本质上都通过调用_class_createInstanceFromZone(...)函数实现,其中最关键的传入参数是Class类型的cls,含义是构建类为cls的对象。代码看起来挺长,实际上仅包含两个操作:

  • 为对象分配cls->instanceSize()大小的内存空间;
  • 构建对象的isa
static __attribute__((always_inline)) 
id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone, 
                              bool cxxConstruct = true, 
                              size_t *outAllocatedSize = nil)
{
    if (!cls) return nil;

    assert(cls->isRealized());

    bool hasCxxCtor = cls->hasCxxCtor();
    bool hasCxxDtor = cls->hasCxxDtor();
    bool fast = cls->canAllocNonpointer();

    size_t size = cls->instanceSize(extraBytes);
    if (outAllocatedSize) *outAllocatedSize = size;

    id obj;
    if (!zone  &&  fast) {
        // ----------- 逻辑分支1 ----------- //
        // 1.1 分配对象内存
        obj = (id)calloc(1, size);
        if (!obj) return nil;
        
        // 1.2 构建对象isa
        obj->initInstanceIsa(cls, hasCxxDtor);
    } 
    else {
        // ----------- 逻辑分支2 ----------- //
        // 2.1 分配对象内存
        if (zone) {
            obj = (id)malloc_zone_calloc ((malloc_zone_t *)zone, 1, size);
        } else {
            obj = (id)calloc(1, size);
        }
        if (!obj) return nil;

        // 2.2 构建对象isa
        obj->initIsa(cls);
    }

    // 若存在cxx语系构造函数,则调用。可忽略
    if (cxxConstruct && hasCxxCtor) {
        obj = _objc_constructOrFree(obj, cls);
    }

    return obj;
}
复制代码

4.3 对象的析构

对象的析构调用对象的rootDealloc()方法,源代码虽然不很多但是整个过程经过了几个函数。总结对象析构所需要的操作如下:

  • 释放对关联对象的引用;
  • 清空 side table 中保存的该对象弱引用地址、对象引用计数等内存管理数据;
  • 释放对象占用的内存;
inline void
objc_object::rootDealloc()
{
    if (isTaggedPointer()) return;  // fixme necessary?

    if (fastpath(isa.nonpointer  &&  
                 !isa.weakly_referenced  &&  
                 !isa.has_assoc  &&  
                 !isa.has_cxx_dtor  &&  
                 !isa.has_sidetable_rc))
    {
        assert(!sidetable_present());
        // 释放对象占用内存
        free(this);
    } 
    else {
        object_dispose((id)this);
    }
}

id 
object_dispose(id obj)
{
    if (!obj) return nil;

    objc_destructInstance(obj);    
    // 释放对象占用内存
    free(obj);

    return nil;
}

void *objc_destructInstance(id obj) 
{
    if (obj) {
        bool cxx = obj->hasCxxDtor();
        bool assoc = obj->hasAssociatedObjects();

        // 若存在cxx语系析构函数,则调用。可忽略
        if (cxx) object_cxxDestruct(obj);

        // 释放关联对象
        if (assoc) _object_remove_assocations(obj);

        // 清空side table中保存的该对象的弱引用地址、及引用计数等内存管理数据
        obj->clearDeallocating();  // 可忽略实现细节
    }

    return obj;
}
复制代码

五、总结

  • Runtime 中对象用objc_object实现,用isa_t类型占用8字节内存空间的isa成员指向对象的类,新版本 runtime 的isa还保存了对象引用计数、是否已析构、是否被弱引用、是否存在关联对象等对象内存管理的关键数据;

  • Runtime 中类用objc_class实现,类也是一个对象,objc_classisa指向类的元类,元类的isa指向根元类,根元类的isa指向根元类自身,该条件可以用于判断类是否为根元类;

  • objc_classsuperclass成员指向类的父类,用于组织类的继承链;

  • objc_class的数据保存在bits成员的有效位域指向的内存空间中,类的编译时决议数据保存在class_ro_t结构体中,运行时决议数据保存在class_rw_t结构体中,class_ro_tclass_rw_tflags成员用于标记类的状态,数据总入口为class_rw_t

  • 类存在懒加载机制,懒加载类先标记为 future class,正式加载 future class 数据需要调用readClass(...)方法,对 future class 进行重映射(remapping);

  • 从镜像加载的类由于只包含编译时决议数据,因此bits成员指向class_ro_t数据结构。必须经过 class realizing,构建类的class_rw_t数据,调整类的instanceSizeinstanceStartivarLayout(为了支持 non-fragile instance variables),以及将类的分类(category)中的方法添加到方法列表;

  • 类的成员变量、方法列表、属性列表、分类的实现及加载、对象内存管理涉及的 side table 将在后续独立文章中详细介绍。




原文地址:访问原文地址
快照地址: 访问文章快照