【iOS】--对象的底层结构

源码

先转一下源码

//

#import <Foundation/Foundation.h>
#import <objc/runtime.h>


@interface LGPerson : NSObject
@property (nonatomic, strong) NSString *KCName;
@end

@implementation LGPerson

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        NSLog(@"Hello, World!");
    }
    return 0;
}

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

编译成功了之后就会出现这样的一个.cpp文件,这就是转换后的C++文件了:

对象

点开.cpp文件之后在里面找到我们刚才定义的继承类:【iOS】--对象的底层结构_第1张图片

  • OC中的LGPerson类底层是struct LGPerson_IMPL结构体
  • OC中@interface LGPerson : NSObject,LGPerson继承NSObject底层是typedef struct objc_object LGPerson;这样体现的

由此可见:OC对象的本质就是结构体。
我们再顺藤摸瓜,找到struct NSObject_IMPL NSObject_IVARS;这个类型:
【iOS】--对象的底层结构_第2张图片
struct NSObject_IMPL结构体事实上是一个Class类型的isa指针,那么也就是说明,每一个新定义的类,它对应的结构体中都会有一个这个Class类型的isa指针。
并且NSObject底层是struct objc_object结构体。【iOS】--对象的底层结构_第3张图片
【iOS】--对象的底层结构_第4张图片

Class superclass; //指向其父类
cache_t cache; //缓存
class_data_bits_t bits; //类的数据

可以看出objc_class是继承objc_object的。

看到这我们先来浅总结一下:

每个类的底层都会有一个Class类的isa指针。
Class底层是struct objc_class *类型,NSObject底层是struct objc_object结构体,id底层是struct objc_object *类型。
struct objc_object的实现是:

struct objc_object {
    Class _Nonnull isa __attribute__((deprecated));
};

也就是说NSObject底层实现的结构体里只有一个成员变量isa,又因为Class底层是struct objc_class *类型,所以 NSObject的本质是objc_class。

id底层实现是struct objc_object 类型,怪不得声明id类型变量时 后面不用再加""了,因为它定义的时候就定义为了一个Class的指针。

  • SEL是struct objc_selector *类型。
  • IMP是void (*)(void )函数指针类型。

【iOS】--对象的底层结构_第5张图片

首先数入参LGPerson * self,SEL _cmd,所有OC方法都有这两个隐藏参数,所以我们可以在OC的方法中使用self指针,但是这两个参数对外是不显示的。
然后可以看到getter和setter里是通过首地址指针+对应成员变量的地址值指针的偏移量的方式取和存的,最终通过(*(NSString **)还原为string类型。取值的过程就是:先拿到当前成员变量的地址,再去取这个地址里面所存的值

objc_class 和 objc_object 有什么关系?

  • 结构体objc_class继承自objc_object,其中objc_object也是一个结构体,而且有一个isa属性,所以objc_class也拥有了isa属性。
  • main.cpp底层编译文件中,NSObject的isa在底层是由class定义的,其中class的底层编码来自于objc_class类型,所以NSObject也拥有了isa属性。
  • NSObject是一个类,用它来初始化一个实例对象 objc,objc满足objc_object的特性(有isa属性),主要是因为isa是由NSObject从objc_class继承过来的,而objc_class继承自objc_object,objc_object有isa属性,所以对象都有一个isa,isa 表示指向,来自于当前的objc_object。
  • objc_object是当前的根对象,所以所有的对象都拥有isa 属性。

objc_object与对象的关系:

  • 所有对象都是以objc_object为模板继承过来的。
  • 所有对象都来自于NSObject,但是其底层是一个objc_object的结构体类型,所以objc_object与对象的关系是继承关系。
    【iOS】--对象的底层结构_第6张图片

类结构

struct objc_class : objc_object {
    ...省略无关代码
    // Class ISA;  //ISA(从objc_object继承过来的)
    Class superclass;  //指向其父类
    cache_t cache;  //缓存
    class_data_bits_t bits;  //类的数据
    
	class_rw_t *data() const {
        return bits.data();
    }
    void setData(class_rw_t *newData) {
        bits.setData(newData);
    }
    ...省略无关代码
}  

从上面的结构体,可以看出:类是一个结构体,里面存放了isa、superClass、cache、bits等。

Class ISA

isa指针保存着指向类对象的内存地址,类对象全局只有一个,因此每个类创建出来的对象都会默认有一个isa属性,保存类对象的地址,也就是class,通过class就可以查询到这个对象的属性和方法,协议等;
isa分两种类型(isa指针是什么含义的时候):

  • 指针型isa:64位的0或者1的整体内容代表所指向的Class的地址,也就是可以通过isa的内容来获得类对象的地址。
  • 非指针型isa:isa的值的部分代表Class的地址,之所以这样是因为我们在寻址过程中,只有三四十位数就可以保证我们寻找到所有Class地址了,多出来的位可以用来存储其他相关内容,来达到节省内存的目的。

每个OC对象都含有一个isa指针,__arm64__之前,isa仅仅是一个指针,保存着对象或类对象内存地址,在__arm64__架构之后,apple对isa进行了优化,变成了一个共用体(union)结构,同时使用位域来存储更多的信息。【iOS】--对象的底层结构_第7张图片
【iOS】--对象的底层结构_第8张图片

  • nonpointer:用来标记这个对象是不是tagpointer类型的对象,因为iOS对oc对象进行了优化处理,有些对象是tagpointer类型的,因此这些对象是没有isa指针的,tagpointer的内存一般是在栈中的,而不是在堆里面;tagpointer对象一般是NSNumber类型的数值较小的数,或NSString类型的较小的字符串。
  • has_assoc:用来标记有没有关联对象。
  • has_cxx_dtor:该对象是否有C++或者Objc的析构器,如果有析构函数,则需要做析构逻辑,如果没有,则可以更快的释放对象。
  • shiftcls:存储的isa指针地址,也就是类对象的地址。
  • magic:用于调试器判断当前对象是真的对象还是没有初始化的空间。
  • weakly_referenced:对象是否被指向或者曾经指向一个ARC的弱变量,没有弱引用的对象可以更快释放。
  • deallocating:标志对象是否正在释放内存。
    has_sidetable_rc:标记对象是否使用了Sidetable,当对象引用计数大于10时,则需要借用该变量存储进位。
  • extra_rc:当表示该对象的引用计数值,实际上是引用计数值减1, 例如:如果对象的引用计数为10,那么extra_rc为9。如果引用计数大于10,则需要使用到下面的 has_sidetable_rc。
union isa_t 
{
    Class cls;
    uintptr_t bits;
    struct {
         uintptr_t nonpointer        : 1;//->表示使用优化的isa指针
         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;//1->在extra_rc存储引用计数将要溢出的时候,借助Sidetable(散列表)存储引用计数,has_sidetable_rc设置成1
         uintptr_t extra_rc          : 19;  //->存储引用计数,实际的引用计数减一,存储的是其对象以外的引用计数
    };
};

【iOS】--对象的底层结构_第9张图片

inline void 
objc_object::initIsa(Class cls, bool nonpointer, UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT bool hasCxxDtor)
{ 
    ASSERT(!isTaggedPointer()); 
    
    isa_t newisa(0);

    if (!nonpointer) {
        newisa.setClass(cls, this);
    } else {
        ASSERT(!DisableNonpointerIsa);
        ASSERT(!cls->instancesRequireRawIsa());


#if SUPPORT_INDEXED_ISA
        ASSERT(cls->classArrayIndex() > 0);
        newisa.bits = ISA_INDEX_MAGIC_VALUE;
        // isa.magic is part of ISA_MAGIC_VALUE
        // isa.nonpointer is part of ISA_MAGIC_VALUE
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
        newisa.bits = ISA_MAGIC_VALUE;
        // isa.magic is part of ISA_MAGIC_VALUE
        // isa.nonpointer is part of ISA_MAGIC_VALUE
#   if ISA_HAS_CXX_DTOR_BIT
        newisa.has_cxx_dtor = hasCxxDtor;
#   endif
        newisa.setClass(cls, this);
#endif
        newisa.extra_rc = 1;
    }

    // This write must be performed in a single store in some cases
    // (for example when realizing a class because other threads
    // may simultaneously try to use the class).
    // fixme use atomics here to guarantee single-store and to
    // guarantee memory order w.r.t. the class index table
    // ...but not too atomic because we don't want to hurt instantiation
    isa() = newisa;
}

源码主要是初始化isa,两种方式:

  • 通过cls初始化:
    非nonpointer,存储着Class、Meta-Class对象的内存地址信息。
  • 通过bits初始化:
    nonpointer,进行一系列的初始化操作。

Class superclass

superclass指向该对象或该类的父类,class*本身也是一个指针,占8字节。

cache_t cache

【iOS】--对象的底层结构_第10张图片
cache缓存,追踪进去看一下cache_t结构体的类型,而不是结构体指针类型(占8字节),就需要计算一下了:

struct cache_t {
    struct bucket_t *_buckets; // 8  散列表
    mask_t _mask;  // 4  散列长度-1
    mask_t _occupied; // 4  已经缓存的方法数量
}
#if __LP64__
typedef uint32_t mask_t;  // x86_64 & arm64 asm are less efficient with 16-bits
#else
typedef uint16_t mask_t;
#endif

struct bucket_t {
private:
    // IMP-first is better for arm64e ptrauth and no worse for arm64.
    // SEL-first is better for armv7* and i386 and x86_64.
#if __arm64__
    MethodCacheIMP _imp;//函数的内存地址
    cache_key_t _key;//SEL作为key
#else

    cache_key_t _key;//SEL作为key
    MethodCacheIMP _imp;//函数的内存地址
#endif
}

同时这里引入了bucket_t(散列表),cache_t哈希表结构,哈希表内部存储的bucket_t,bucket_t中存储的是SEL和IMP的键值对。
【iOS】--对象的底层结构_第11张图片

class_data_bits_t bits

class_data_bits_t作为属性bits的类型,也是个结构体,其数据结构如下:

struct class_data_bits_t {
    friend objc_class;//这里声明objc_class为class_data_bits_t的友元类,使得objc_class可以访问class_data_bits_t中的私有方法
    uintptr_t bits;
    ......

从源码中可以看到,class_data_bits_t有一个属性uintptr_t bits,bits的类型是uintptr_t类型数据,那么uintptr_t是什么数据结构呢?以下是通过查看一些资料得到的解释:

是无符号整数类型,能够存储指针。这通常意味着它与指针的大小相同。 它可选地在C++ 11和更高版本的标准中定义。 想要一个可以保存体系结构指针类型的整数类型的常见原因是对指针执行特定于整数的操作,或者通过将指针提供为整数“句柄”来模糊指针的类型

这里面其实利用位域存储数据,简单来说因为这个bits跟指针大小相同,在iOS中指针大小是8个字节,也就是64位,但是通常单一数据无法用满这64位的,会有很多空位,造成空间浪费。通过位运算,把不同的数据按不同的位置存进这64位内存空间的不同位置里,这样就可以提高内存利用率。这里的bits其实就是这样的,他不只存储class_rw_t结构体指针,还存储其他的信息。

看到这里我们获取会有一些疑惑,那就是类结构里面我们没有看到方法列表、属性列表甚至成员列表等,那这些东西存在哪里呢?由前面分析知道bits里有个class_rw_t指针,而且objc_class里面的data和getData方法都涉及到class_rw_t的读写:

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

class_rw_t

【iOS】--对象的底层结构_第12张图片

struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint16_t witness;
#if SUPPORT_INDEXED_ISA
    uint16_t index;
#endif

    explicit_atomic<uintptr_t> ro_or_rw_ext;

    Class firstSubclass;
    Class nextSiblingClass;

private:
    using ro_or_rw_ext_t = objc::PointerUnion<const class_ro_t, class_rw_ext_t, PTRAUTH_STR("class_ro_t"), PTRAUTH_STR("class_rw_ext_t")>;

    const ro_or_rw_ext_t get_ro_or_rwe() const {
        return ro_or_rw_ext_t{ro_or_rw_ext};
    }

    void set_ro_or_rwe(const class_ro_t *ro) {
        ro_or_rw_ext_t{ro, &ro_or_rw_ext}.storeAt(ro_or_rw_ext, memory_order_relaxed);
    }

    void set_ro_or_rwe(class_rw_ext_t *rwe, const class_ro_t *ro) {
        // the release barrier is so that the class_rw_ext_t::ro initialization
        // is visible to lockless readers
        rwe->ro = ro;
        ro_or_rw_ext_t{rwe, &ro_or_rw_ext}.storeAt(ro_or_rw_ext, memory_order_release);
    }

    class_rw_ext_t *extAlloc(const class_ro_t *ro, bool deep = false);

public:
    void setFlags(uint32_t set)
    {
        __c11_atomic_fetch_or((_Atomic(uint32_t) *)&flags, set, __ATOMIC_RELAXED);
    }

    void clearFlags(uint32_t clear)
    {
        __c11_atomic_fetch_and((_Atomic(uint32_t) *)&flags, ~clear, __ATOMIC_RELAXED);
    }

    // set and clear must not overlap
    void changeFlags(uint32_t set, uint32_t clear)
    {
        ASSERT((set & clear) == 0);

        uint32_t oldf, newf;
        do {
            oldf = flags;
            newf = (oldf | set) & ~clear;
        } while (!CompareAndSwap(oldf, newf, &flags));
    }

    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() {
        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 {
            return extAlloc(v.get<const class_ro_t *>(&ro_or_rw_ext));
        }
    }

    class_rw_ext_t *deepCopy(const class_ro_t *ro) {
        return extAlloc(ro, true);
    }

    const class_ro_t *ro() const {
        auto v = get_ro_or_rwe();
        if (slowpath(v.is<class_rw_ext_t *>())) {
            return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->ro;
        }
        return v.get<const class_ro_t *>(&ro_or_rw_ext);
    }

    void set_ro(const class_ro_t *ro) {
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            v.get<class_rw_ext_t *>(&ro_or_rw_ext)->ro = ro;
        } else {
            set_ro_or_rwe(ro);
        }
    }

    const method_array_t methods() const {
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->methods;
        } else {
            return method_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseMethods};
        }
    }

    const property_array_t properties() const {
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->properties;
        } else {
            return property_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProperties};
        }
    }

    const protocol_array_t protocols() const {
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->protocols;
        } else {
            return protocol_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProtocols};
        }
    }
}

从这个结构体可以看到,其中有const method_array_t methods()、const property_array_t properties() 等这样的方法,因此可以推测类结构中的方法列表、属性列表是从这里获取的。以const method_array_t methods()为例,获取类的实例方法也是通过调用methods()来获取的。比如说runtime函数class_copyMethodList,其代码如下:

Method *
class_copyMethodList(Class cls, unsigned int *outCount)
{
    unsigned int count = 0;
    Method *result = nil;

    if (!cls) {
        if (outCount) *outCount = 0;
        return nil;
    }

    mutex_locker_t lock(runtimeLock);
    const auto methods = cls->data()->methods();

    ASSERT(cls->isRealized());

    count = methods.count();

    if (count > 0) {
        auto iterator = methods.signedBegin();
        auto end = methods.signedEnd();

        result = (Method *)malloc((count + 1) * sizeof(Method));

        count = 0;
        for (; iterator != end; ++iterator) {
            result[count++] = _method_sign(&*iterator);
        }
        result[count] = nil;
    }

    if (outCount) *outCount = count;
    return result;
}

其中const auto methods = cls->data()->methods()就是通过class_rw_t的methods()方法获取实例方法列表。但是在class_rw_t结构体中,我们并没有看到结构体里由存储方法列表相关的属性。反而是在methods()方法里看到读取方法列表是从class_rw_ext_t或者class_ro_t结构体。其实不只是methods,包括properties和protocols也是一样的。那么他们之间到底有什么关系呢?

接下来我们先来看看class_rw_ext_t和class_ro_t的数据结构,看看为什么class_rw_t可以从他们这里读取类相关信息。

class_rw_ext_t

class_rw_ext_t是个结构体,其数据结构如下:

struct class_rw_ext_t {
    const class_ro_t *ro;
    method_array_t methods;
    property_array_t properties;
    protocol_array_t protocols;
    char *demangledName;
    uint32_t version;
};

由此可知,结构体class_rw_ext_t中的确有方法列表、属性列表和协议等信息,而且还有一个class_ro_t指针,接下来继续看class_ro_t。

class_ro_t

【iOS】--对象的底层结构_第13张图片
以下是class_ro_t的结构体:

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;
    // This field exists only when RO_HAS_SWIFT_INITIALIZER is set.
    _objc_swiftMetadataInitializer __ptrauth_objc_method_list_imp _swiftMetadataInitializer_NEVER_USE[0];
    _objc_swiftMetadataInitializer swiftMetadataInitializer() const {
        if (flags & RO_HAS_SWIFT_INITIALIZER) {
            return _swiftMetadataInitializer_NEVER_USE[0];
        } else {
            return nil;
        }
    }
    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_t不仅有baseMethodList、 baseProtocols、baseProperties等信息,还有成员变量const ivar_list_t * ivars等其他信息。在了解他们的结构之后,我们从结构上简单做一下它们之间关系的分析:首先class_rw_t有一个指针ro_or_rw_ext,ro_or_rw_ext指向可能是class_rw_ext_t或者是class_ro_t。ro_or_rw_ext指针的作用是,当读取类相关信息时,会优先判断是否指向class_rw_ext_t,如果是class_rw_ext_t就从它读取,没有就是class_ro_t,直接从class_ro_t读取。那么在程序运行中到底ro_or_rw_ext什么时候是class_rw_ext_t,什么时候是class_ro_t呢?其实WWDC2020有过对这三个数据结构的介绍,下面会做简单的总结。

class_ro_t & class_rw_t & class_rw_ext_t & Class之间的关系

要了解它们之间的关系,我们首先要了解几个关键跟它们相关的概念。

  • 名词解析

从字面意思解读:class_ro_t中的“ro”代表只读;class_rw_t中的“rw”代表可读可写;class_rw_ext_t中的“rw_ext”代表可读可写的扩展。
干净内存和脏内存:干净内存内存指的是内存一旦加载就不会被改变。反之,脏内存就是在运行时会被修改的。

  • 关系释疑

Class本身是运行时加载的,在运行时会被改变,所以本身Class就是属于脏内存。那么如果想要获取Class的干净内存,也就是编译时确定的数据结构包括方法列表、成员变量等的,该怎么办?
这其实就是class_ro_t的作用。因为class_ro_t是只读,意味着 class_ro_t是从mach-o读取类的数据之后,就不会被改变。那如果我们想在运行时修改类的信息,比如添加方法,比如加载category怎么办呢?那这时候就有一个与之对应的class_rw_t结构,class_rw_t可以在运行时存储类的信息,可读可写的,可以在运行时修改。
说到这里,好像还漏掉一个结构class_rw_ext_t,这个东西又是干什么用的呢?存在的意义是什么?其实还是跟运行时有关。实际上在我们的app运行中,需要运行时修改的类是非常少的,据统计平均大概就10%左右。那也就是说大部分只需要读取class_ro_t中的数据就够了,少部分才需要修改。因此才会有class_rw_ext_t这个扩展的结构体。class_rw_ext_t的作用是这样的:当我们需要修改类结构时,比如添加方法、加载category等时,class_rw_t回去开辟一个额外的空间rwe(class_rw_ext_t),用于存储新的方法和class_ro_t中的方法等信息。这样做的目的有一个好处就是,对于绝大部分类是不需要这个开辟class_rw_ext_t这个结构体,节省内存。

  • 创建时机
    那这几个结构分别是什么时候创建的呢?这里就设计到类的加载流程。首先类在app启动的时候会被映射到内存,这时候会先创建Class(objc_class)结构,然后把类编译时的类数据映射到class_ro_t,class_ro_t结构体指针存储到Class的bits指针中,我们前面提到类的bits中存储的是class_rw_t指针,实际上在类初始化之前这里存储的是class_ro_t,等到类初始化的时候会创建一个class_rw_t结构,然后通过data()从bits中读取class_ro_t,然后class_rw_t通过set_ro(const class_ro_t *ro)把指针ro_or_rw_ext指向这个class_ro_t,然后Class通过setData()把class_rw_t指针存储到bits里面。然后在运行时根据需要,根据extAllocIfNeeded或extAlloc创建class_rw_ext_t,然后把class_ro_t关联到class_rw_ext_t,这就是大概流程。

category不能添加成员变量的原因

因为category是运行时添加的,他只能操作class_rw_t结构,但是class_rw_t结构没有添加成员变量的入口。成员变量是存储在class_ro_t中的,是无法被修改的,所以category就不能添加成员变量。

类数据的存储

通过上述的源码解析,都应该清楚了类数据的存储结构,大概总结总结一下结构关系,如下图所示:
【iOS】--对象的底层结构_第14张图片
【iOS】--对象的底层结构_第15张图片

你可能感兴趣的:(ios,objective-c,c++)