美文网首页
iOS底层探索004-类分析

iOS底层探索004-类分析

作者: 星星1024 | 来源:发表于2020-09-16 15:49 被阅读0次

iOS底层探索-目录

1. 类的分析

主要分析两个部分:isa的走向和继承关系

isa分析

类的isa走向,参考这篇文章

关系图

总结得出:

  • 对象isa指向
  • isa指向元类
  • 元类isa指向根元类,即NSObject
  • 根元类isa指向它自己

NSObject根元类是否是一个?

验证1:


验证2:

    Class class1 = [Person class];
    Class class2 = [Person alloc].class;
    Class class3 = object_getClass([Person alloc]);
    NSLog(@"\n%p \n%p \n%p ", class1, class2, class3);

打印结果:

0x10d92d558 
0x10d92d558 
0x10d92d558

小结: 从结果中看出, 打印的地址都是同一个,所以NSObject只有一份,即NSObject(根元类)在内存中永远只存在一份*

objc_class & objc_object

通过上面分析我们发现对象都有isa, Why?

下面我们看下关于objc_classobjc_object的定义:

struct NSObject_IMPL {
    Class isa;
};
typedef struct objc_class *Class;
struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class _Nullable super_class                              OBJC2_UNAVAILABLE;
    const char * _Nonnull name                               OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE; //被废除的
/* Use `Class` instead of `struct objc_class *` */

我们发现在新的源码中被废弃了, 在781源码中看最新的

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() const {
        return bits.data();
    }
    void setData(class_rw_t *newData) {
        bits.setData(newData);
    }

    void setInfo(uint32_t set) {
        ASSERT(isFuture()  ||  isRealized());
        data()->setFlags(set);
    }

    void clearInfo(uint32_t clear) {
        ASSERT(isFuture()  ||  isRealized());
        data()->clearFlags(clear);
    }

    // set and clear must not overlap
    void changeInfo(uint32_t set, uint32_t clear) {
        ASSERT(isFuture()  ||  isRealized());
        ASSERT((set & clear) == 0);
        data()->changeFlags(set, clear);
    }
  • NSObject的底层编译是NSObject_IMPL结构体
  • Classisa的指针类型, 由objc_class定义的类型
  • objc_class是一个结构体。在iOS中,所有的Class都是以objc_class为模板创建的
  • 新版本可知:objc_class结构体类型是继承自objc_object
  • objc_object是一个结构体, 且有isa属性, 故objc_class也有isa属性
  • NSObject是由objc_class定义的,也有isa属性
  • 实例对象objc是由NSObject初始化的,也有isa属性

**总结: **

  • objc_object对象的关系是继承关系
  • 对象都是由objc_object继承来的(万物皆对象-> 以objc_objectobjc_class者为模板创建的对象,都有isa属性)
  • 对象, , 元类都有isa属性

面试题:

  1. 类存在几份?
  • 由于类的信息在内存中永远只存在一份,所以类对象只有一份(验证1和验证2)
  1. objc_object对象的关系
  • 所有的对象都是以objc_object为模板继承过来的
  • 所有的对象是来自NSObject(OC) ,但是真正到底层的是一个objc_object(C/C++)的结构体类型(都有isa)

2. 类的结构分析

类结构体的定义:

struct objc_class : objc_object {
    // Class ISA; //继承自objc_object的isa,占8字节
    Class superclass; //Class是由objc_object定义的,是一个指针,占8字节
    
    // 从类型无法判断大小.是个结构体,大小由内部属性决定, 结构体指针才是8字节
    cache_t cache;             // formerly cache pointer and vtable 
    
    //只有首地址经过上面3个属性的内存大小总和的平移,才能获取到bits
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags

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

    void setInfo(uint32_t set) {
        ASSERT(isFuture()  ||  isRealized());
        data()->setFlags(set);
    }

    void clearInfo(uint32_t clear) {
        ASSERT(isFuture()  ||  isRealized());
        data()->clearFlags(clear);
    }

    // set and clear must not overlap
    void changeInfo(uint32_t set, uint32_t clear) {
        ASSERT(isFuture()  ||  isRealized());
        ASSERT((set & clear) == 0);
        data()->changeFlags(set, clear);
    }

计算 cache 大小

通过上面定义我们只要知道了cache的大小,就可以根据内存偏移读取bits的信息

struct cache_t {
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
    explicit_atomic<struct bucket_t *> _buckets;
    explicit_atomic<mask_t> _mask;
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
    explicit_atomic<uintptr_t> _maskAndBuckets;
    mask_t _mask_unused;
    
    // How much the mask is shifted by.
    static constexpr uintptr_t maskShift = 48;
    
    // Additional bits after the mask which must be zero. msgSend
    // takes advantage of these additional bits to construct the value
    // `mask << 4` from `_maskAndBuckets` in a single instruction.
    static constexpr uintptr_t maskZeroBits = 4;
    
    // The largest mask value we can store.
    static constexpr uintptr_t maxMask = ((uintptr_t)1 << (64 - maskShift)) - 1;
    
    // The mask applied to `_maskAndBuckets` to retrieve the buckets pointer.
    static constexpr uintptr_t bucketsMask = ((uintptr_t)1 << (maskShift - maskZeroBits)) - 1;
    
    // Ensure we have enough bits for the buckets pointer.
    static_assert(bucketsMask >= MACH_VM_MAX_ADDRESS, "Bucket field doesn't have enough bits for arbitrary pointers.");
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
    // _maskAndBuckets stores the mask shift in the low 4 bits, and
    // the buckets pointer in the remainder of the value. The mask
    // shift is the value where (0xffff >> shift) produces the correct
    // mask. This is equal to 16 - log2(cache_size).
    explicit_atomic<uintptr_t> _maskAndBuckets;
    mask_t _mask_unused;

    static constexpr uintptr_t maskBits = 4;
    static constexpr uintptr_t maskMask = (1 << maskBits) - 1;
    static constexpr uintptr_t bucketsMask = ~maskMask;
#else
#error Unknown cache mask storage type.
#endif
    
#if __LP64__
    uint16_t _flags;
#endif
    uint16_t _occupied;
  • if bucket_t *结构体指针8字节, mask_tuint32_t类型4字节
  • else if uintptr_tunsigned long类型8字节, mask_tuint32_t类型4字节
  • uint16_t _flagsunsigned short类型2字节
  • uint16_t _occupiedunsigned short类型2字节

cache大小: 8 + 4 + 2 + 2 = 16字节

获取bits信息

根据上面计算可知,获取bits信息,需要首地址平移8+8+16=32字节

获取首地址方法(两种)


获取首地址方法.png

获取bits首地址(平移32字节)

平移32,获取bits

属性信息

获取属性
  • 通过打印我们发现properties只存储了属性,而没有成员变量,那么成员变量存储到哪里了呢?

成员变量

成员变量.png

小结:

  • 通过{}定义的成员变量,会存储在bits属性中,通过bits --> data() -->ro() --> ivars获取成员变量列表
  • 通过@property定义的属性,也会存储在bits属性中,通过bits --> data() --> properties() --> list获取属性列表,其中只包含属性

方法信息

实例方法

实例方法.png

类方法

类方法.png
  • 实例方法存储在类的bits属性中,通过bits --> methods() --> list获取实例方法列表
  • bits包含属性setget方法
  • 类的类方法存储在元类的bits属性中, 通过bits --> methods() --> list获取类方法列表

相关文章

网友评论

      本文标题:iOS底层探索004-类分析

      本文链接:https://www.haomeiwen.com/subject/kpyvektx.html