【iOS】—— 类和对象底层探索

类与对象

1.对象的本质

在之前我们的学习中应该了解过,oc对象的本质就是结构体,到底是一个怎样的结构体呢?

我们打开源码来看看:

/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;

/// Represents an instance of a class.
struct objc_object {
    
    
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};

再往进点一层看看:

struct objc_object {
    
    
private:
    isa_t isa;

public:
...

除去方法部分,我们可以发现成员变量只有一个isa_t类型的isa
而isa_t这个 类型只有一个成员变量,存储了对象所属类的信息[Class cls]

**总结:**对象的本质除了其本身含有的方法列表,它有一个isa指针用来存储所属类的信息。

我们来仔细看一下这个isa_t

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

    uintptr_t bits;// 八个字节 64位

private:
    // Accessing the class requires custom ptrauth operations, so
    // force clients to go through setClass/getClass by making this
    // private.
    Class cls;

public:
#if defined(ISA_BITFIELD)
    struct {
    
    
        ISA_BITFIELD;  // defined in isa.h
    };

    bool isDeallocating() {
    
    
        return extra_rc == 0 && has_sidetable_rc == 0;
    }
    void setDeallocating() {
    
    
        extra_rc = 0;
        has_sidetable_rc = 0;
    }
#endif

    void setClass(Class cls, objc_object *obj);
    Class getClass(bool authenticated);
    Class getDecodedClass(bool authenticated);
};

我们发现它是一个共用体,共用体和结构体其实都可以定义很多变量,区别就是共用体占用的内存为变量最宽的内存。共同体中所有的成员变量共同使用这一块内存,很节省空间,但容易覆盖。

其实所有的数据都存储在成员变量bits里面,这个结构体利用位域来增加代码可读性,让我们看看bits对应的位上分别存储什么。

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

# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL
#   define ISA_MAGIC_MASK  0x000003f000000001ULL
#   define ISA_MAGIC_VALUE 0x000001a000000001ULL
#   define ISA_BITFIELD                                                      \
      uintptr_t nonpointer        : 1;          /* isa是否经过优化,如果指为0,代表无优化,就是一个isa指针,
        64位全部用来存储对象所属类的地址,如果为1,则采取当前位分配。*/                             \
      uintptr_t has_assoc         : 1;      /*判断有无关联对象,如果没有快速销毁对象。 */                                 \
      uintptr_t has_cxx_dtor      : 1;      /*判断当前对象有无析构函数,更快销毁 */                                 \
      uintptr_t shiftcls          : 33; /*当前对象所属类的地址信息 */ \
      uintptr_t magic             : 6;     /*标记调试时,当前对象是否初始化 */                                  \
      uintptr_t weakly_referenced : 1;  /*标记弱引用表里是否有当前对象弱指针数组,也就是是否被若指针指向,以及是否有弱引用*/                                    \
      uintptr_t deallocating      : 1;         /*当前对象是否释放。 */                              \
      uintptr_t has_sidetable_rc  : 1;  /*引用计数表里面是否有当前对象的引用记数*/                                      \
      uintptr_t extra_rc          : 19/*对象的引用计数 - 1,存不下了就回放到引用计数表中*/
#   define RC_ONE   (1ULL<<45)
#   define RC_HALF  (1ULL<<18)

# elif __x86_64__
#   define ISA_MASK        0x00007ffffffffff8ULL
#   define ISA_MAGIC_MASK  0x001f800000000001ULL
#   define ISA_MAGIC_VALUE 0x001d800000000001ULL
#   define ISA_BITFIELD                                                        \
      uintptr_t nonpointer        : 1;                                         \
      uintptr_t has_assoc         : 1;                                         \
      uintptr_t has_cxx_dtor      : 1;                                         \
      uintptr_t shiftcls          : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \
      uintptr_t magic             : 6;                                         \
      uintptr_t weakly_referenced : 1;                                         \
      uintptr_t deallocating      : 1;                                         \
      uintptr_t has_sidetable_rc  : 1;                                         \
      uintptr_t extra_rc          : 8
#   define RC_ONE   (1ULL<<56)
#   define RC_HALF  (1ULL<<7)

# else
#   error unknown architecture for packed isa
# endif

// SUPPORT
  • 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。
    请添加图片描述

2.类的本质

来看看类的底层结构体:

struct objc_class : objc_object {
    
    
  objc_class(const objc_class&) = delete;
  objc_class(objc_class&&) = delete;
  void operator=(const objc_class&) = delete;
  void operator=(objc_class&&) = delete;
    // 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
    
	...
	
} 

objc_class继承于objc_object,换句话说,类的本质也是个对象,其中isa指针式其继承自objc_object的,所以代码里才注释了// Class ISA;

在之前一直搞不太懂类对象,实例对象,元类这些之间的关系和区别,在这部分学习之后,就可以理解了。

isa是结构体,其中会存储类的地址,其并不是指针。

我们来康康对象,类,元类的示意图:
请添加图片描述

元类的定义

OC对象在发送消息时,运行时库会追寻着对象的isa指针得到对象所属的类。这个类包含了能应用于这个类的所有实例方法以及指向父类的指针,以便可以找到父类的实例方法。运行时库检查这个类和其父类的方法列表,找到与消息对应的方法。 编译器会将消息转换为消息函数objc_msgSend进行调用

我们可以知道,OC的类其实也是一个对象,一个对象就要有一个它属于的类,意味着类也要有一个isa指针,指向其所属的类。那么类的类是什么呢?就是我们所说的元类(MetaClass),所以,元类就是类对象的所属类。

所以从消息机制的层面来讲:

  • 当你给对象发消息时,消息会寻找这个对象的类的方法列表
  • 当你给类发消息时,消息是在寻找这个类的元类的方法列表

OC的类信息存放在哪里?

答:对象方法、属性、成员变量、协议信息,存放在class对象中
类方法存放在meta-class对象中(元类对象和class内存结构是一样的 但是用途不一样 主要有类方法的类信息 其他为空的)
成员变量的具体值存放在instance对象中

isa初始化过程

下面进入initInstanceIsa的探索,首先查看它的源码实现:

inline void 
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
    
    
    ASSERT(!cls->instancesRequireRawIsa());
    ASSERT(hasCxxDtor == cls->hasCxxDtor());

    initIsa(cls, true, hasCxxDtor);
}

initInstanceIsa的源码实现中,主要是调用了initIsa,继续跳到initIsa的源码:

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_t 类型的 newisa 实例, 做了赋值操作后,返回了 newisa。

该源码解释了上面初始化isa的两种方式:

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

请添加图片描述

ISA_BITFIELD

isa_t 中还有一个成员变量 是 结构体 ISA_BITFIELD, 这个宏定义对应 arm64 和 x86_64 即 iOS 和 MacOS 两个端的实现。 ISA_BITFIELD 通过位域存储信息,具体存储信息如下:

# if __arm64__
// ARM64 simulators have a larger address space, so use the ARM64e
// scheme even when simulators build for ARM64-not-e.
#   if __has_feature(ptrauth_calls) || TARGET_OS_SIMULATOR
#     define ISA_MASK        0x007ffffffffffff8ULL
#     define ISA_MAGIC_MASK  0x0000000000000001ULL
#     define ISA_MAGIC_VALUE 0x0000000000000001ULL
#     define ISA_HAS_CXX_DTOR_BIT 0
#     define ISA_BITFIELD                                                      \
        uintptr_t nonpointer        : 1;                                       \
        uintptr_t has_assoc         : 1;                                       \
        uintptr_t weakly_referenced : 1;                                       \
        uintptr_t shiftcls_and_sig  : 52;                                      \
        uintptr_t has_sidetable_rc  : 1;                                       \
        uintptr_t extra_rc          : 8
#     define ISA_HAS_INLINE_RC    1
#     define RC_HAS_SIDETABLE_BIT 55
#     define RC_ONE_BIT           (RC_HAS_SIDETABLE_BIT+1)
#     define RC_ONE               (1ULL<<RC_ONE_BIT)
#     define RC_HALF              (1ULL<<7)
#   else
#     define ISA_MASK        0x0000000ffffffff8ULL
#     define ISA_MAGIC_MASK  0x000003f000000001ULL
#     define ISA_MAGIC_VALUE 0x000001a000000001ULL
#     define ISA_HAS_CXX_DTOR_BIT 1
#     define ISA_BITFIELD                                                      \
        uintptr_t nonpointer        : 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 unused            : 1;                                       \
        uintptr_t has_sidetable_rc  : 1;                                       \
        uintptr_t extra_rc          : 19
#     define ISA_HAS_INLINE_RC    1
#     define RC_HAS_SIDETABLE_BIT 44
#     define RC_ONE_BIT           (RC_HAS_SIDETABLE_BIT+1)
#     define RC_ONE               (1ULL<<RC_ONE_BIT)
#     define RC_HALF              (1ULL<<18)
#   endif

#   if TARGET_OS_SIMULATOR
#     define ISA_MASK_NOSIG ISA_MASK
#   elif TARGET_OS_OSX
#     define ISA_MASK_NOSIG 0x00007ffffffffff8ULL
#   else
#     define ISA_MASK_NOSIG 0x0000000ffffffff8ULL
#   endif

# elif __x86_64__
#   define ISA_MASK        0x00007ffffffffff8ULL
#   define ISA_MAGIC_MASK  0x001f800000000001ULL
#   define ISA_MAGIC_VALUE 0x001d800000000001ULL
#   define ISA_HAS_CXX_DTOR_BIT 1
#   define ISA_BITFIELD                                                        \
      uintptr_t nonpointer        : 1;                                         \
      uintptr_t has_assoc         : 1;                                         \
      uintptr_t has_cxx_dtor      : 1;                                         \
      uintptr_t shiftcls          : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \
      uintptr_t magic             : 6;                                         \
      uintptr_t weakly_referenced : 1;                                         \
      uintptr_t unused            : 1;                                         \
      uintptr_t has_sidetable_rc  : 1;                                         \
      uintptr_t extra_rc          : 8
#   define ISA_HAS_INLINE_RC    1
#   define RC_HAS_SIDETABLE_BIT 55
#   define RC_ONE_BIT           (RC_HAS_SIDETABLE_BIT+1)
#   define RC_ONE               (1ULL<<RC_ONE_BIT)
#   define RC_HALF              (1ULL<<7)

# else
#   error unknown architecture for packed isa
# endif

// SUPPORT_PACKED_ISA
#endif

为来更直观的理解上面 位域 ISA_BITFIELD存储的信息, 我们画个图来解析一下以上这段很长的代码。

arm64架构下的isa:
请添加图片描述
x86_64架构下的isa:
请添加图片描述

我们发现源码中经常会用到assert()函数,该函数的作用如下:
其作用是如果它的条件返回错误,则终止程序执行。
assert的作用是先计算表达式 expression ,如果其值为假(即为0),那么它先向stderr打印一条出错信息,然后通过调用 abort 来终止程序运行。

初始化isa_t的代码为:

newisa.bits = ISA_INDEX_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE

这个里面说的很明白 news.bits初始化时候只设置了nonpointer,magic两个部分,其余部分都没有进行设置,故值都为0。

class对象

我们先来看看几点注意:
1.Class ObjectClass = [[NSObject class] class]; 返回的还是class对象,并不是meta-class对象。- (Class)class, +(Class)class返回的就是类对象

        //instance实例对象
        NSObject *object1 = [[NSObject alloc] init];
        NSObject *object2 = [[NSObject alloc] init];

        //class对象,类对象
        //objectClass1~5都是NSObject的类对象
        Class objectClass1 = [object1 class];
        Class objectClass2 = [object2 class];
        Class objectClass3 = [NSObject class];
        Class objectClass4 = object_getClass(object1);
        Class objectClass5 = object_getClass(object2);
            
        //元类对象(将类对象当作参数传入进去)
        Class objectMetaClass = object_getClass([NSObject class]);
        Class objectMetaClass2 = [[NSObject class] class];
            
        //判断是不是元类对象

        NSLog(@"instance - %p %p", object1, object2);
        NSLog(@"class - %p %p %p %p %p %d", objectClass1,objectClass2, objectClass3, objectClass4, objectClass5, class_isMetaClass(objectClass3 ));
        NSLog(@"mateClass - %p %p %d",objectMetaClass, objectMetaClass2, class_isMetaClass(objectMetaClass));

输出结果:
在这里插入图片描述
无论多少次class方法得到的都还是类函数。

2.元类对象和class内存结构是一样的 但是用途不一样 主要有类方法的类信息 其他为空的
3.类对象在内存中有且仅有一个对象 主要包括 isa指针 super Class指针 类的属性信息 类的对象方法信息 类的协议信息 类的成员变量信息

我们接着来看objc_class的源码部分:

struct objc_class : objc_object {
    
    
  objc_class(const objc_class&) = delete;
  objc_class(objc_class&&) = delete;
  void operator=(const objc_class&) = delete;
  void operator=(objc_class&&) = delete;
    // 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
	....
}

刚刚讲完了isa,接下来看看superclass:
superclass很简单,superclass指向该对象或该类的父类,class*本身也是一个指针,占8字节。

cache_t cache

请添加图片描述
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
}

cache_t的类型,由代码中标注的出来的内存大小,可以算出最后计算出的cache类的内存大小为 12 + 2 + 2 = 16 字节。

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

请添加图片描述

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,其代码如下:

/***********************************************************************
* class_copyMethodList
* fixme
* Locking: read-locks runtimeLock
**********************************************************************/
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;
}

这个方法比较好理解,将所有的方法放在result里,并返回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_rw_ext_t是个结构体,其数据结构如下:

struct class_rw_ext_t {
    
    
    DECLARE_AUTHED_PTR_TEMPLATE(class_ro_t)
    class_ro_t_authed_ptr<const class_ro_t> ro;
    method_array_t methods;
    property_array_t properties;
    protocol_array_t protocols;
    const char *demangledName;
    uint32_t version;
};

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

class_rw_ext_t作用:当我们需要修改类结构时,比如添加方法、加载category等时,class_rw_t回去开辟一个额外的空间rwe(class_rw_ext_t),用于存储新的方法和class_ro_t中的方法等信息。这样做的目的有一个好处就是,对于绝大部分类是不需要这个开辟class_rw_ext_t这个结构体,节省内存。

class_ro_t

请添加图片描述

以下是class_ro_t的结构体:

struct class_ro_t {
    
    
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;
#ifdef __LP64__
    uint32_t reserved;
#endif

    union {
    
    
        const uint8_t * ivarLayout;
        Class nonMetaclass;
    };

    explicit_atomic<const char *> name;
    WrappedPtr<method_list_t, method_list_t::Ptrauth> baseMethods;
    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;
        }
    }

    const char *getName() const {
    
    
        return name.load(std::memory_order_acquire);
    }

    class_ro_t *duplicate() const {
    
    
        bool hasSwiftInitializer = flags & RO_HAS_SWIFT_INITIALIZER;

        size_t size = sizeof(*this);
        if (hasSwiftInitializer)
            size += sizeof(_swiftMetadataInitializer_NEVER_USE[0]);

        class_ro_t *ro = (class_ro_t *)memdup(this, size);

        if (hasSwiftInitializer)
            ro->_swiftMetadataInitializer_NEVER_USE[0] = this->_swiftMetadataInitializer_NEVER_USE[0];

#if __has_feature(ptrauth_calls)
        // Re-sign the method list pointer.
        ro->baseMethods = baseMethods;
#endif

        return ro;
    }

    Class getNonMetaclass() const {
    
    
        ASSERT(flags & RO_META);
        return nonMetaclass;
    }

    const uint8_t *getIvarLayout() const {
    
    
        if (flags & RO_META)
            return nullptr;
        return ivarLayout;
    }
};

这里面我们发现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读取。

class_ro_t 和 class_rw_t 和 rw_ext_t之间的关系

class_ro_t中的“ro”代表只读;class_rw_t中的“rw”代表可读可写;class_rw_ext_t中的“rw_ext”代表可读可写的扩展。
从生成时机的角度来说,
ro编译阶段生成,rw运行的时候生成。从存储的内容角度来讲,ro中有方法、属性、协议和成员变量,而rw中并没有成员变量。rw中的方法属性协议的取值方法中,也是通过取ro或者rwe中的值来获得。ro中的方法、属性、协议都是base,也就是只有本类中的方法属性和协议。
有一篇文章把这部分讲的很明白:iOS八股文(四)类对象的结构(下)

补充一个概念:干净内存和脏内存:干净内存内存指的是内存一旦加载就不会被改变。反之,脏内存就是在运行时会被修改的。

Class本身是运行时加载的,在运行时会被改变,所以本身Class就是属于脏内存。那么如果想要获取Class的干净内存,也就是编译时确定的数据结构包括方法列表、成员变量等的,该怎么办?
这其实就是class_ro_t的作用。因为class_ro_t是只读,意味着 class_ro_t是从mach-o读取类的数据之后,就不会被改变。那如果我们想在运行时修改类的信息,比如添加方法,比如加载category怎么办呢?那这时候就有一个与之对应的class_rw_t结构,class_rw_t可以在运行时存储类的信息,可读可写的,可以在运行时修改。
来看看下面两张图:
请添加图片描述

请添加图片描述
请添加图片描述
请添加图片描述

rw_ext_t生成条件

  • 使用分类的类。
  • 使用Runtime API动态修改类的结构的时候。

在遇到以上2种情况的时候,类的结构(属性、协议、方法)发生改变,原有的ro(Claer Memory,便宜)已经不能继续记录类的属性、协议、方法信息了,于是系统重新生成可读可写的内存结构rw_ext(Dirty Memory, 比较贵),来存放新的类结构。

类方法的存储

类方法是存放在元类里面的。

元类里面存放的内容只有类方法,那么为什么不把类方法存放在类对象中?或者说设计元类的目的是什么呢?

  • 单一职责设计原理。 实例对象存储成员变量的值,类对象存放,实例方法、协议、成员变量、属性,元类对象存放类方法,各司其职,互不影响。
  • 复用msgSend消息发送机制。

类方法、实例方法是在上层的定义,在底层并不区分类方法实例方法,但在runtime这一层,需要承接上层类方法和实例方法,对接到底层方法调用。使用了msgSend,如果msgSend的时候需要再区分类对象,实例对象,会在内部增加判读逻辑,从而降低了效率,有了元类的存在,问题迎刃而解。

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

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

这篇文章写的有点小乱,以后还会做以改正和补充的

最后再来看看最重要的这张图:
请添加图片描述

猜你喜欢

转载自blog.csdn.net/m0_62386635/article/details/129909446