iOS--OC底层原理文章汇总
如题,今天来探究下类的一些信息,isa指针的指向就是该类对象的元类,每一个类都是它的元类的对象,元类是对类对象的描述。首先来分析一下代码
//类对象内存存在个数为 1
void lgTestClassNum(){
Class class1 = [LGPerson class];
Class class2 = [LGPerson alloc].class;
Class class3 = object_getClass([LGPerson alloc]);
Class class4 = [LGPerson alloc].class;
NSLog(@"\n%p-\n%p-\n%p-\n%p",class1,class2,class3,class4);
}
void lgTestNSObject(){
// NSObject实例对象
NSObject *object1 = [NSObject alloc];
// NSObject类
Class class = object_getClass(object1);
// NSObject元类
Class metaClass = object_getClass(class);
// NSObject根元类
Class rootMetaClass = object_getClass(metaClass);
// NSObject根根元类
Class rootRootMetaClass = object_getClass(rootMetaClass);
NSLog(@"\n%p 实例对象\n%p 类\n%p 元类\n%p 根元类\n%p 根根元类",object1,class,metaClass,rootMetaClass,rootRootMetaClass);
}
打印结果分析之:
2020-09-14 23:24-isa分析[1763:92018] <LGPerson: 0x102055210>
2020-09-14 23:24-isa分析[1763:92018]
0x100002588-
0x100002588-
0x100002588-
0x100002588
2020-09-14 23:24-isa分析[1763:92018]
0x1005135b0 实例对象
0x7fffb1bd0140 类
0x7fffb1bd00f0 元类
0x7fffb1bd00f0 根元类
0x7fffb1bd00f0 根根元类
可以看到lgTestClassNum
中打印的类对象地址都是同一个,说明:类对象存在个数仅有一份。在lgTestNSObject
中实例对象地址与类的地址,以及元类、根元类、根根元类的地址是不一样的,其中元类、根元类、根根元类的地址则是一样的。这是为什么呢?
在系列文章前几章中我们了解了isa的结构,类的首地址就是isa,isa中的shiftcls
存储着类的一些信息,isa在类中存在这非常重要的关系,继续通过LLDB分析之。
isa指向:对象->类对象->元类对象->根元类对象,根元类对象的isa指向根元类自身
元类可以存储类的自有信息,可供子类继承的类信息。
实例对象没有继承关系,只有类与类间才有继承关系;
根元类的父类为NSObject,NSObject 继承的地址没有,所以父类为nil。
实例对象的isa指向它的类对象Subclass,Subclass继承自它的父类Superclass,而Subclass的isa指向它的元类Subclass(meta),类和元类的地址是一样的。
拓展:objc_class vs objc_object 关系?
- 类的底层编码是—— objc_class
- 对象的根对象—— objc_object
- objc_class 又继承自 objc_object ,所有class也是对象——万物皆对象。
- 所有的对象,类,元类都有isa。
结构体类型objc_class 继承自objc_object类型,其中objc_object也是一个结构体,且有一个isa属性,所以objc_class也拥有了isa属性
mian.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(结构体) 是 当前的 根对象,所有的对象都有这样一个特性 objc_object,即拥有isa属性
以下是objc-781源码中objc_object
定义:
objc_object 和实例对象的关系?
所有的对象都是继承NSObject,NSObject又继承自底层的objc_object(C/C++)结构体类型,底层的是没有对象而是结构体。
类的内存 + 对象的内存
先拓展:内存偏移
在探究类的内存信息时,需要用到内存偏移相关知识,所以先介绍下内存偏移。
- 变量的地址差
定义两个普通变量,打印它们以及变量地址
这个很好理解,内存上有一块数字未100的块,有两个变量都指向这个内存,虽然值相同,但是它们的变量的地址是不一样的,这是因为值发生了一个浅拷贝
。但是观察到两个内存的地址偏差很小为4
,这是一个int字节的长度,这个长度差取决于b的类型长度。这样对一个相同的值的指向,将其指针地址连续存储,是优化存储的一种小手段。 - 对象的地址差
定义一个Book类,实例化两个对象,打印它们的地址,前者为对象的指针地址,指向的事开辟的内存空间;后者的二级指针,存储的则是对象地址的地址(or指针的指针)。由于该类没有属性、成员变量,系统则将&book1,&book2
存储在连续的地址,由于二级指针为纯指针,其字节长度就是isa的长度,所以两个二级指针差为8;通过这样的指针地址差是可以访问到两个相邻内存地址的。
- 数组的指针差
定义一个数组,然后定义一个指针指向这个数组
&c:
获取c的首地址
&c[0]:
获取c数组首个元素的地址
&c[1]:
获取c数组第2个元素的地址
*d = c;
将c的首地址赋值给d,d+1
则是对d的地址进行内存偏移,偏移1个单位则是对应于c的下一个元素地址。
通过以上认识到:数组首个元素的指针地址作为数组的指针地址。*
通过首地址的偏移,可挨个取出数组元素,偏移量则由元素类型的字节数决定。
借用style_月月博主简单明了的一张图,能很好的理解了内存偏移
类objc_object的结构信息
// 注意new 、old版本差,此版本为objc4-781
struct objc_class : objc_object {
// Class ISA; // 默认8字节
Class superclass; // 8字节
cache_t cache; // 8 + 4 + 2 + 2 // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
/*篇幅有限,省略N多代码,主要代码分析上面即可*/
};
提前剧透式的获取类的关键信息:class_data_bits_t
,class_data_bits_t
存储了类中的属性(property)、方法(method)、协议(protocol),这是类结构信息中的关键部分,通过之前的LLDB我们是可以获得类对象的首地址的,通过内存偏移的方式获取class_data_bits_t
。
进入cache_t源码分析之,它里面有相当多的代码,get其关键属性信息,则就是:struct cache_t
的字节长度
struct cache_t {
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
explicit_atomic<struct bucket_t *> _buckets; // 是一个结构体指针类型,占8字节
explicit_atomic<mask_t> _mask; //是mask_t 类型,而 mask_t 是 unsigned int 的别名,占4字节
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
explicit_atomic<uintptr_t> _maskAndBuckets; //是指针,占8字节
mask_t _mask_unused; //是mask_t 类型,而 mask_t 是 uint32_t 类型定义的别名,占4字节
#if __LP64__
uint16_t _flags; //是uint16_t类型,uint16_t是 unsigned short 的别名,占 2个字节
#endif
uint16_t _occupied; //是uint16_t类型,uint16_t是 unsigned short 的别名,占 2个字节
所以要想获取bits内存,就是要将类的首地址(同isa)进行内存偏移即可获取.So -> isa
: 8字节 + superClass
: 8字节 + cache_t( _buckets / _maskAndBuckets
: 8 + _mask / _mask_unused
: 4 + _flags
:2 + _occupied
:2 = 16)= 32字节(16进制:0x20)。
isa属性:继承自objc_object的isa,占 8字节
superclass 属性:Class类型,Class是由objc_object定义的,是一个指针,占8字节
cache属性:简单从类型class_data_bits_t目前无法得知,而class_data_bits_t是一个结构体类型,结构体的内存大小需要根据内部的属性来确定,而结构体指针才是8字节
bits属性:只有首地址经过上面3个属性的内存大小总和的平移,才能获取到bits
通过LLDB分析:
定义一个Person类
@interface Person : NSObject
@property (nonatomic,copy) NSString * name;
@property (nonatomic,copy) NSString * age;
-(void)sayHello;
+(void)sayHappy;
@end
- 获取bits方法一:在源码中找
class_data_bits_t
,打印bits探索其结构。
(lldb) p/x bits // 16进制打印bits内存地址
(class_data_bits_t) $0 = (bits = 0x0000000100002028)
(lldb) p $0->data() // $0: 别名,调用data()方法
(class_rw_t *) $1 = 0x0000000100002028
Fix-it applied, fixed expression was:
$0.data()
(lldb) p *$1
(class_rw_t) $2 = {
flags = 129
witness = 40
ro_or_rw_ext = {
std::__1::atomic<unsigned long> = 40
}
firstSubclass = nil
nextSiblingClass = 0x0000000100000fa0
}
// 找到ro文件后续探索继续查看方法二找bits
- 获取bits方法二:在调用
Person * person =[Person alloc];
之后下断点,通过打印类指针地址,利用内存偏移原理,加之上面分析的获取bits要在cls地址基础上偏移32字节(16进制:0x20),即在
(Class) $0 = 0x0000000100002250 LGPerson
(lldb) p/x (class_data_bits_t *)0x0000000100002270 // 在$0基础上加0x20
(class_data_bits_t *) $3 = 0x0000000100002270 // 则可得到bits
方法二完整LLDB
(lldb) p/x person.class
(Class) $0 = 0x0000000100002250 LGPerson
(lldb) p/x (class_data_bits_t *)0x0000000100002270 // 在$0寄出上加0x20,
(class_data_bits_t *) $3 = 0x0000000100002270
(lldb) p *$3
(class_data_bits_t) $4 = (bits = 4302628644)
(lldb) p $4->data() /// $4: 别名,调用data()方法
(class_rw_t *) $5 = 0x000000010074e720
Fix-it applied, fixed expression was:
$4.data()
(lldb) p *$5
(class_rw_t) $6 = {
flags = 2148007936
witness = 1
ro_or_rw_ext = {
std::__1::atomic<unsigned long> = 4294975664
}
firstSubclass = nil
nextSiblingClass = NSUUID
}
(lldb) p $6.properties() // 打印第属性list
(const property_array_t) $7 = {
list_array_tt<property_t, property_list_t> = {
= {
list = 0x00000001000021d8
arrayAndFlag = 4294975960
}
}
}
(lldb) p $7.list
(property_list_t *const) $8 = 0x00000001000021d8
(lldb) p *$8
(property_list_t) $9 = {
entsize_list_tt<property_t, property_list_t, 0> = {
entsizeAndFlags = 16
count = 2
first = (name = "name", attributes = "T@\"NSString\",C,N,V_name")
}
}
(lldb) p $9.get(1) // 打印第一个属性
(property_t) $10 = (name = "age", attributes = "T@\"NSString\",C,N,V_age")
(lldb) p $6.methods()
(const method_array_t) $11 = {
list_array_tt<method_t, method_list_t> = {
= {
list = 0x00000001000020f8
arrayAndFlag = 4294975736
}
}
}
(lldb) p $11.list
(method_list_t *const) $12 = 0x00000001000020f8
(lldb) p *$12 // 获取了Person类的 method_list_t
(method_list_t) $13 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 26
count = 6
first = {
name = "sayHello"
types = 0x0000000100000f75 "v16@0:8"
imp = 0x0000000100000cd0 (KCObjc`-[LGPerson sayHello])
}
}
}
(lldb) p $13.get(0) // 打印出对象方法 sayHello
(method_t) $14 = {
name = "sayHello"
types = 0x0000000100000f75 "v16@0:8"
imp = 0x0000000100000cd0 (KCObjc`-[LGPerson sayHello])
}
(lldb) p $13.get(1)
(method_t) $15 = {
name = ".cxx_destruct"
types = 0x0000000100000f75 "v16@0:8"
imp = 0x0000000100000da0 (KCObjc`-[LGPerson .cxx_destruct])
}
(lldb) p $13.get(2)
(method_t) $16 = {
name = "name"
types = 0x0000000100000f89 "@16@0:8"
imp = 0x0000000100000ce0 (KCObjc`-[LGPerson name])
}
(lldb) p $13.get(3)
(method_t) $17 = {
name = "setName:"
types = 0x0000000100000f91 "v24@0:8@16"
imp = 0x0000000100000d10 (KCObjc`-[LGPerson setName:])
}
(lldb) p $13.get(4)
(method_t) $18 = {
name = "age"
types = 0x0000000100000f89 "@16@0:8"
imp = 0x0000000100000d40 (KCObjc`-[LGPerson age])
}
(lldb) p $13.get(5)
(method_t) $19 = {
name = "setAge:"
types = 0x0000000100000f91 "v24@0:8@16"
imp = 0x0000000100000d70 (KCObjc`-[LGPerson setAge:])
}
(lldb) p $13.get(6)
Assertion failed: (i < count), function get, file /Users/tl/逻辑教育/iOS底层/Day5/20200914-大师班第5天-类原理分析-资料/01--课堂代码/003-iskindof面试题/runtime/objc-runtime-new.h, line 438.
error: Execution was interrupted, reason: signal SIGABRT.
The process has been returned to the state before expression evaluation.
在 p $13.get(6)
执行时,发送了越界,方法list并没有类方法sayHappy()
,这是由于对象方法存在类中,而类方法存在其元类中。下一节继续探索为什么。