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
  • 可以得出内存地址对应的关系如下:

iOS之深入解析类Class的底层原理_第1张图片

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

类的结构

一、类结构组成
  • 要分析类的结构,可以 运用 clang 将目标文件编译成 cpp(C++文件)
    编译 cpp(C++文件) 的方法请参考之前的博文:iOS之深入解析对象isa的底层原理。
  • 打开 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 又占多少字节呢?

iOS之深入解析类Class的底层原理_第2张图片

  • cache_t 结构体如下:
    分析可以看出: cache_t 所占的字节说为16个字节,因此我们要拿到bits只需将首地址偏移8 + 8 +16 = 32字节便可得到;
	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));
	};
二、类结构成员分析
  • 类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个字节
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
  • class_data_bits_t bits 是一个结构体,结构体 bits 有一个方法 bits.data() ,可以看到方法 data() 是 class_rw_t 类型的,查看 class_rw_t 类型,会发现我们要找的属性和方法就在里面。
  • 那么类的属性最可能是存在 bits 里面,继续点击 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);
	    }
	
	......省略其他信息......
  • 根据上面的指针和内存偏移内容可以知道,如果我们知道类的地址,并且属性是按照顺序依次排列的,只要我们知道isa、superclass、cache的内存大小,那我们就可以通过内存偏移来得到bits的内存地址,然后取出bits里面的内容。

iOS之深入解析类Class的底层原理_第3张图片

  • 成员变量操作函数,主要包含以下函数:
    • class_getInstanceVariable函数,它返回一个指向包含name指定的成员变量信息的objc_ivar结构体的指针(Ivar)。
    • class_getClassVariable函数,目前没有找到关于Objective-C中类变量的信息,一般认为Objective-C不支持类变量。注意,返回的列表不包含父类的成员变量和属性。Objective-C不支持往已存在的类中添加实例变量,因此不管是系统库提供的提供的类,还是我们自定义的类,都无法动态添加成员变量。但如果我们通过运行时来创建一个类的话,又应该如何给它添加成员变量呢?这时我们就可以使用class_addIvar函数了。
    • class_addIvar只能在objc_allocateClassPair函数与objc_registerClassPair之间调用。另外,这个类也不能是元类。成员变量的按字节最小对齐量是1<
    • 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 );

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

一、Apple 官方图分析
  • Apple官方的实例对象、类、元类关系图如下:

iOS之深入解析类Class的底层原理_第4张图片

  • 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)关系如下:

iOS之深入解析类Class的底层原理_第5张图片

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

总结

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

你可能感兴趣的:(iOS高级进阶,Swift高级进阶,Objective-C底层原理,内存偏移,类结构)