iOS之深入解析类Class的底层原理

内存偏移

  • 定义一个数组并打印数组中的元素地址:
	int a[4] = {
    
    1,2,3,4};
	int *b = a;
	NSLog(@"%p - %p - %p - %p", &a, &a[0], &a[1], &a[2]);
    NSLog(@"%p - %p - %p - %p", b, b+1, b+2, b+3);
  • 打印结果如下:
	0x7ffeefbff510 - 0x7ffeefbff510 - 0x7ffeefbff514 - 0x7ffeefbff518
	0x7ffeefbff510 - 0x7ffeefbff514 - 0x7ffeefbff518 - 0x7ffeefbff51c
  • 由上面结果分析可知:

    • 由 &a 与 &a[0] 的打印结果相同可知, 数组的首地址存着数组的第一个元素
    • int 占用 4 个字节,由打印 b 的指针可以看出,0x7ffeefbff510 -> 0x7ffeefbff51 4地址偏移 4 个字节, 通过对地址的偏移,一样可以找到数组a中的元素
  • 再通过 lldb 测试由 b 拿到数组 a 中的元素:

	(lldb) po *b
	1
	(lldb) po *(b+1)
	2
	(lldb) po *(b+2)
	3
	(lldb) po *(b+3)
	4
  • 可以得出内存地址对应的关系如下:

在这里插入图片描述

  • 可以通过 地址偏移指向接下来连续的内存地址 ,取到自己需要的相应元素。

类的结构

一、类结构组成
  • 要分析类的结构,可以 运用 clang 将目标文件编译成 cpp(C++文件)
    编译 cpp(C++文件) 的方法请参考之前的博文:iOS之深入解析对象isa的底层原理

  • 新建一个 YDWPerson 类,并声明属性和方法,并新建一个 YDWDriver 继承于YDWPerson,如下:

	@interface YDWPerson : NSObject {
    
    
	    NSString *name;
	}
	
	@property (nonatomic, copy) NSString *nickName;
	@property (nonatomic, assign) int age;
	
	- (void)goShopping;
	
	@end
	@interface YDWDriver : YDWPerson
	
	@end
  • 通过 LLDB 调试:
	// person 的内存
	(lldb) x/4gx person
	// 0x001d800100002325 为 person 的 isa 指针地址
	0x101415f70: 0x001d800100002325 0x0000000000000000
	0x101415f80: 0x0000000000000000 0x0000000000000000
	// person 首地址
	(lldb) p/x person
	(YDWPerson *) $1 = 0x0000000101415f70
	// 获取类信息,即类的指针地址:创建的 person 的类 YDWPerson
	(lldb) p/x 0x001d800100002325 & 0x00007ffffffffff8ULL
	// 0x0000000100002320 为类的指针地址
	(unsigned long long) $2 = 0x0000000100002320
	// 根据类的指针地址获取类信息
	(lldb) po 0x0000000100002320
	YDWPerson
	
	// 通过类信息地址获取类的内存分布
	(lldb) x 0x0000000100002320
	0x100002320: f8 22 00 00 01 00 00 00 40 41 33 00 01 00 00 00  ."......@A3.....
	0x100002330: 40 e4 32 00 01 00 00 00 00 00 00 00 2c 80 00 00  @.2.........,...
	// 通过类 YDWPerson 获取类的内存分布
	(lldb) x YDWPerson.class
	0x100002320: f8 22 00 00 01 00 00 00 40 41 33 00 01 00 00 00  ."......@A3.....
	0x100002330: 40 e4 32 00 01 00 00 00 00 00 00 00 2c 80 00 00  @.2.........,...
	// 通过object_getClass方法获取类的内存分布
	(lldb) x object_getClass(person)
	0x100002320: f8 22 00 00 01 00 00 00 40 41 33 00 01 00 00 00  ."......@A3.....
	0x100002330: 40 e4 32 00 01 00 00 00 00 00 00 00 2c 80 00 00  @.2.........,...
	// YDWPerson 的内存信息(0x100002320为类的首地址)
	(lldb) x/4gx 0x100002320
	0x100002320: 0x00000001000022f8 0x0000000100334140
	0x100002330: 0x000000010032e440 0x0000802c00000000
	// YDWPerson内存中isa的指针指向(0x00000001000022f8为类的isa指针地址)
	(lldb) po 0x00000001000022f8
	YDWPerson
	
	// 类信息(指针地址)
	(lldb) p/x 0x00000001000022f8 & 0x00007ffffffffff8ULL
	// 0x00000001000022f8 为元类的指针地址
	(unsigned long long) $9 = 0x00000001000022f8
	// 元类的信息
	(lldb) po 0x00000001000022f8
	YDWPerson
  • 分析以上LLDB调试,不难发现:p/x 0x001d800100002325 & 0x00007ffffffffff8ULL 与 p/x 0x00000001000022f8 & 0x00007ffffffffff8ULL 的结果都是 YDWPerson,这是为什么呢?其实,这是因为 元类 导致的。

    • 0x001d800100002325 是 person 对象的 isa 指针地址 ,其&后得到的结果是创建 person 的类 YDWPerson;
    • 0x00000001000022f8 是 isa 中 获取的类信息所指的类的 isa 的指针地址 ,即 YDWPerson 类的类的 isa 指针地址 ,在Apple中,可以称 YDWPerson 类的类为 元类
  • 打开 cpp 文件,类的结构定义如下:

	struct objc_class : objc_object {
    
    
	    Class ISA;         		   // 8字节
	    Class superclass;  		   // 结构体指针8字节
	    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();
	    }
	/**此处省略代码*/
	}
	
	typedef struct objc_class *Class;
	struct objc_object {
    
    
	private:
	    isa_t isa;
	public:
	    // ISA() assumes this is NOT a tagged pointer object
	    Class ISA();
	    ...
	}
  • 从上面可以看出类有四个成员:
    • 第一个是isa;
    • 第二个父类指针;
    • 第三个是缓存;
    • 第四个是bits,是一个结构体;
  • 分析上面的 objc 源码可得:
    • Class objc_class 类型, objc_class 类型继承自 objc_object 类型, objc_object 类型有一个 isa 的成员变量。
    • objc_class 结构体是继承于 objc_object 结构体,自然也就有 isa 的成员变量,这是源自于父类,并且 objc_class 结构体的 isa 是指向父类 objc_object 结构体的,这也就说明了类也是一种 类对象
  • 由上面内存偏移的分析可知,如果要获取 class_data_bits_t bits ,只需要知道对首地址偏移多少便能获取到,Class 定义为结构体,我们可以知道 isa、superclass 各占8个字节,那么 bitsclass_data_bits_t 又占多少字节呢?

在这里插入图片描述

  • cache_t 结构体如下:
	struct cache_t {
    
    
	    struct bucket_t *_buckets;// 结构体8个字节
	    mask_t _mask;//typedef uint32_t mask_t; 由此可知mask_t占用4个字节
	    mask_t _occupied;         // 4个字节
	
	public: // 方法不占内存
	    struct bucket_t *buckets();
	    mask_t mask();
	    mask_t occupied();
	    void incrementOccupied();
	    void setBucketsAndMask(struct bucket_t *newBuckets, mask_t newMask);
	    void initializeToEmpty();
	
	    mask_t capacity();
	    bool isConstantEmptyCache();
	    bool canBeFreed();
	
	    static size_t bytesForCapacity(uint32_t cap);
	    static struct bucket_t * endMarker(struct bucket_t *b, uint32_t cap);
	
	    void expand();
	    void reallocate(mask_t oldCapacity, mask_t newCapacity);
	    struct bucket_t * find(cache_key_t key, id receiver);
	
	    static void bad_cache(id receiver, SEL sel, Class isa) __attribute__((noreturn));
	};
  • 计算前两个属性 bucket_t、mask 或 _maskAndBuckets、_mask_unused 的内存大小,有以下两种情况:

    • buckets 类型是 struct bucket_t * ,是结构体指针类型,占 8 字节;
      mask mask_t 类型,而 mask_t unsigned int 的别名,占 4 字节;
    • _maskAndBuckets uintptr_t 类型,它是一个指针,占 8 字节;
      _mask_unused mask_t 类型,而 mask_t uint32_t 类型定义的别名,占 4 字节;
    • ③ bucket_t、mask 或 _maskAndBuckets、_mask_unused 两种情况,所占的内存都是 12 字节;
  • _flags uint16_t 类型, uint16_t unsigned short 的别名,占 2 字节;

  • _occupied uint16_t 类型, uint16_t unsigned short 的别名,占 2 字节;

  • 分析可以看出: cache_t 所占的字节说为 12 + 2 + 2 = 16 个字节,因此要拿到 bits 只需 将首地址偏移 8 + 8 +16 = 32 个字节 便可得到;

二、类结构成员分析
  • C/OC 的数据类型所占的内存如下:
C OC 32位 64位
bool BOOL(64位) 1 1
signed char (_signed char)int8_t、BOOL(32位) 1 1
unsigned char Boolean 1 1
short int16_t 2 2
unsigned short unichar 2 2
int、int32_t NSInteger(32位)、boolean_t(32位) 4 4
unsigned int NSUInteger(32位)、boolean_t(64位) 4 4
long NSInteger(64位) 4 8
unsigned long NSUInteger(64位) 4 8
long long int64_t 8 8
float CGFloat(32位) 4 4
double CGFloat(64位) 8 8
  • objc_class 的结构成员如下:
	struct objc_class : objc_object {
    
    
	    Class ISA;         		   // 8字节
	    Class superclass;  		   // 结构体指针8字节
	    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();
	    }
	/**此处省略代码*/
	}
	
	typedef struct objc_class *Class;
  • 进入 objc_object 里面可以看到,占用 8 个字节;
	struct objc_object {
    
    
	private:
	    isa_t isa;
	    ...
	}
  • Class superclass 父类,是一个指针,占用 8 个字节;
	typedef struct objc_class *Class;
  • cache_t cache 结构体,占16个字节;
	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;
	    ... 都是静态变量,不计入结构体大小
	#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;
	   ... 都是静态变量,不计入结构体大小
	#else
	#error Unknown cache mask storage type.
	#endif
	    
	#if __LP64__
	    uint16_t _flags;
	#endif
	    uint16_t _occupied;
	    ...都是方法和静态变量,不计入结构体大小
	}
  • buckets、mask、flags 如下:
	// buckets:指针类型占8个字节
	#if __LP64__
	typedef uint32_t mask_t;  // x86_64 & arm64 asm are less efficient with 16-bits
	#else
	typedef uint16_t mask_t;
	#endif
	
	// mask:uint32_t类型,4个字节
	typedef unsigned int uint32_t;
	
	// flags:uint16_t类型,2个字节
	typedef unsigned short uint16_t;
  • occupied:uint16_t类型,占 2 个字节;
  • class_data_bits_t bits 是一个结构体,结构体 bits 有一个方法 bits.data() ,可以看到方法 data() 是 class_rw_t 类型的,查看 class_rw_t 类型,会发现我们要找的属性和方法就在里面;
	struct class_rw_t {
    
    
	    // Be warned that Symbolication knows the layout of this structure.
	    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
	
	    void setFlags(uint32_t set) 
	    {
    
    
	        OSAtomicOr32Barrier(set, &flags);
	    }
	
	    void clearFlags(uint32_t clear) 
	    {
    
    
	        OSAtomicXor32Barrier(clear, &flags);
	    }
	
	......省略其他信息......
  • 那么类的属性列表,即 property_list 最可能是存在 bits 里面,继续点击 class_rw_t ,如下:
	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 *>()->methods;
        } else {
    
    
            return method_array_t{
    
    v.get<const class_ro_t *>()->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 *>()->properties;
        } else {
    
    
            return property_array_t{
    
    v.get<const class_ro_t *>()->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 *>()->protocols;
        } else {
    
    
            return protocol_array_t{
    
    v.get<const class_ro_t *>()->baseProtocols};
        }
    }
  • 根据上面的指针和内存偏移内容可以知道,如果我们知道类的地址,并且属性是按照顺序依次排列的,只要我们知道isa、superclass、cache的内存大小,那我们就可以通过内存偏移来得到bits的内存地址,然后取出bits里面的内容。

在这里插入图片描述

  • 成员变量操作函数,主要包含以下函数:
    • class_getInstanceVariable函数,它返回一个指向包含name指定的成员变量信息的objc_ivar结构体的指针(Ivar)。
    • class_getClassVariable函数,目前没有找到关于Objective-C中类变量的信息,一般认为Objective-C不支持类变量。注意,返回的列表不包含父类的成员变量和属性。Objective-C不支持往已存在的类中添加实例变量,因此不管是系统库提供的提供的类,还是我们自定义的类,都无法动态添加成员变量。但如果我们通过运行时来创建一个类的话,又应该如何给它添加成员变量呢?这时我们就可以使用class_addIvar函数了。
    • class_addIvar只能在objc_allocateClassPair函数与objc_registerClassPair之间调用。另外,这个类也不能是元类。成员变量的按字节最小对齐量是1<<alignment。这取决于ivar的类型和机器的架构。如果变量的类型是指针类型,则传递log2(sizeof(pointer_type))。
    • class_copyIvarList函数,它返回一个指向成员变量信息的数组,数组中每个元素是指向该成员变量信息的objc_ivar结构体的指针。这个数组不包含在父类中声明的变量。outCount指针返回数组的大小。需要注意的是,我们必须使用free()来释放这个数组。
	// 获取类中指定名称实例成员变量的信息
	Ivar class_getInstanceVariable ( Class cls, const char *name );
	
	// 获取类成员变量的信息
	Ivar class_getClassVariable ( Class cls, const char *name );
	
	// 添加成员变量
	BOOL class_addIvar ( Class cls, const char *name, size_t size, uint8_t alignment, const char *types );
	
	// 获取整个成员变量列表
	Ivar * class_copyIvarList ( Class cls, unsigned int *outCount );


  • 属性操作函数,主要包含以下函数:
	// 获取指定的属性
	objc_property_t class_getProperty ( Class cls, const char *name );
	
	// 获取属性列表
	objc_property_t * class_copyPropertyList ( Class cls, unsigned int *outCount );
	
	// 为类添加属性
	BOOL class_addProperty ( Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount );
	
	// 替换类的属性
	void class_replaceProperty ( Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount );


实例对象、类、元类关系分析

一、什么是元类?
  • 对象的 isa 是指向类,类的其实也是一个对象,可以称为 类对象 ,其 isa 的位域 指向苹果定义的元类
  • 元类是系统给的,其定义和创建都是由编译器完成,在这个过程中,类的归属来自于 元类
  • 元类是 类对象 的类,每个类都有一个独一无二的元类用来存储类方法的相关信息;
  • 元类本身是没有名称的,由于与类相关联,所以使用了同类名一样的名称。
二、Apple 官方图分析
  • Apple官方的实例对象、类、元类关系图如下:

在这里插入图片描述

  • isa 的指向关系:

    • 实例对象(Instance of Subclass) 的isa指向的是 类(class)
    • 类对象(class) 的isa指向的 元类(Meta class)
    • 元类(Meta class) 指向 根元类(Root metal class)
    • 根元类(Root metal class) 指向 自己
    • NSObject的父类是 nil ,根元类的父类是 NSObject
  • superclass 的指向关系:

    • 类(subClass) 继承于 父类(superClass)
    • 父类(superClass) 继承于 根类(RootClass) ,此时的根类是指 NSObject
    • 根类继承于 nil
  • 元类之间的继承关系如下:

    • 子类的元类(metal SubClass) 继承于父类的 元类(metal SuperClass)
    • 父类的元类(metal SuperClass) 继承于 根元类(Root metal Class)
    • 根元类(Root metal Class) 继承于 根类(Root class) ,此时的根类是 NSObject
三、源码分析

在 objc/runtime.h 中 objc_class 结构体如下:

	struct objc_class {
    
    
        Class isa  OBJC_ISA_AVAILABILITY;
        
        #if !__OBJC2__
        Class super_class                       OBJC2_UNAVAILABLE;  // 父类
        const char *name                        OBJC2_UNAVAILABLE;  // 类名
        long version                            OBJC2_UNAVAILABLE;  // 类的版本信息,默认为0
        long info                               OBJC2_UNAVAILABLE;  // 类信息,供运行期使用的一些位标识
        long instance_size                      OBJC2_UNAVAILABLE;  // 该类的实例变量大小
        struct objc_ivar_list *ivars            OBJC2_UNAVAILABLE;  // 该类的成员变量链表
        struct objc_method_list **methodLists   OBJC2_UNAVAILABLE;  // 方法定义的链表
        struct objc_cache *cache                OBJC2_UNAVAILABLE;  // 方法缓存
        struct objc_protocol_list *protocols    OBJC2_UNAVAILABLE;  // 协议链表
        #endif
	} OBJC2_UNAVAILABLE; 
  • 在 objc-runtime-new.h 中 objc_class 结构体如下:
	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);
	    }
	    // 此处省略以下源码...
  • 继续查看 objc_object 源码如下:
	struct objc_object {
    
    
	    Class _Nonnull isa __attribute__((deprecated));
	};
	struct objc_object {
    
    
	    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
	};
	struct objc_object {
    
    
	private:
	    isa_t isa;
	
	public:
  • objc_class 与 objc_object 关系如下:
    • 结构体类型 objc_class 继承于 objc_object 类型,其中 objc_object 也是一个结构体,且有一个 isa 属性,所以 objc_class 也拥有了 isa 属性;
    • 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 是当前的根对象,所有的对象都有这样一个特性 objc_object,即拥有 isa 属性;
  • 指向元类的指针(isa):在OC中所有的类其实也是一个对象,那么这个对象也会有一个所属的类,这个类就是元类,也就是结构体里面isa指针所指的类。
    • 元类的定义:元类就是类对象的类。每个类都有自己的元类,因为每个类都有自己独一无二的方法。
      ① 当你给对象发送消息时,消息是在寻找这个对象的类的方法列表(实例方法)。
      ② 当你给类发消息时,消息是在寻找这个类的元类的方法列表(类方法)。
    • 那元类的类是什么呢?元类,就像之前的类一样,它也是一个对象。也可以调用它的方法,自然的,这就意味着它必须也有一个类。
      所有的元类都使用根元类(继承体系中处于顶端的类的元类)作为它们的类,这就意味着所有NSObject的子类(大多数类)的元类都会以NSObject的元类作为他们的类
      根据这个规则,所有的元类使用根元类作为他们的类,根元类的元类则就是它自己,也就是说基类的元类的isa指针指向他自己。
    • class_isMetaClass函数,如果是cls是元类,则返回YES;如果否或者传入的cls为Nil,则返回NO。
	// 判断给定的Class是否是一个元类
	BOOL class_isMetaClass ( Class cls );
  • 指向元类的指针(isa)关系如下:

在这里插入图片描述

  • 指向父类的指针(super_class):指向该类的父类,如果该类已经是最顶层的根类(如NSObject或NSProxy),则super_class为NULL。
    class_getSuperclass函数,当cls为Nil或者cls为根类时,返回Nil。不过通常我们可以使用NSObject类的superclass方法来达到同样的目的。
	// 获取类的父类
	Class class_getSuperclass ( Class cls );
  • objc_class、objc_object、isa、object、NSObject 的相互关系如下:

在这里插入图片描述

四、NSObject 的唯一性
  • 通过以下代码查看运行结果:
	Class class1 = [YDWPerson class];
    Class class2 = [YDWPerson alloc].class;
    Class class3 = object_getClass([YDWPerson alloc]);
    NSLog(@"\n%p \n%p \n%p", class1, class2, class3);
  • 打印结果如下:
	0x100002328 
	0x100002328 
	0x100002328
  • 明显看出:打印的地址都是同一个,所以 NSObject 只有一份,即 NSObject(根元类)在内存中永远只存在一份。

总结

  • objc_class 与 NSObject 的关系:
    NSObject 就是一个类,其本质是 objc_class,也就是说 NSObject 是 objc_class 的一种类型。
  • objc_object 与NSObject 的关系:
    NSObject 是 OC 的类型,objc_object 是 c 的类型。
    NSObject 是对 objc_object的封装,OC 的底层编译是 C,也就是会转成 objc_object。

猜你喜欢

转载自blog.csdn.net/Forever_wj/article/details/108544548