前言
连续指针内存偏移
int array[] = {1,2,4};
int *b = array;
NSLog(@"%p-%p-%p-%p",&array,&array[0],&array[1],&array[2]);
NSLog(@"%p-%p-%p",b,b+1,b+2);
for (int i = 0; i < 3; i++) {
int value = *(b+i);
NSLog(@"value-%d",value);
}
2019-12-21 21:06:11.914923+0800 XDTest[2663:291951] 0x7ffeefbff5bc-0x7ffeefbff5bc-0x7ffeefbff5c0-0x7ffeefbff5c4
2019-12-21 21:06:11.915605+0800 XDTest[2663:291951] 0x7ffeefbff5bc-0x7ffeefbff5c0-0x7ffeefbff5c4
2019-12-21 21:06:11.915673+0800 XDTest[2663:291951] value-1
2019-12-21 21:06:11.915729+0800 XDTest[2663:291951] value-2
2019-12-21 21:06:11.915756+0800 XDTest[2663:291951] value-4
- 指针
b
的地址就是array
数组的首地址。- 通过指针偏移可以找到接下来的连续内存地址。
类结构分析
1. 万物皆对象
typedef struct objc_class *Class;
struct objc_class : objc_object{};
- 从源码中我们就可以知道类
Class
的本质就是objc_class
。objc_class
继承自objc_object
,验证万物皆对象。
Q1: objc_class
与NSObject
的关系?
NSObject
就是一个类,其本质是objc_class
。
Q2: objc_object
与NSObject
的关系?
NSObject
是OC
的类型,objc_object
是c
的类型。
NSObject
是对objc_object
的封装。
2. 类结构
struct objc_class : objc_object {
// Class ISA; //8
Class superclass; //8
cache_t cache; //16 // 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();
}
...省略其他的信息...
};
3. 类结构成员分析
3.1 Class ISA
一个被注释的成员,代表是从父类继承过来的,所占用8字节。
struct objc_object {
private:
isa_t isa;
...省略其他的信息...
};
3.2 Class superclass
指向父类的指针,Class
本身就是一个指针,所占用8字节。
3.3 cache_t cache
顾名思义,是存储缓存的对象,所占用16字节
truct cache_t {
struct bucket_t *_buckets; //指针占用8字节
mask_t _mask; // int32 占用4字节
mask_t _occupied; //占用4字节
...省略其他的信息...
};
3.4 class_data_bits_t bits
一个结构体,我们发现类的一些相关的信息在前面三个成员里面都看不到,有此我们可以分析出来,类的相关属性,成员变量,方法都在这个结构体里面。
4. class_data_bits_t
深入
通过对类的结构体的分析isa
、superclass
、cache
这些属性里面是看不到与我们自定义的属性等等有关系的,因此我们对准bits
来探索。
- 我们在类的结构体也看到了下面的这行代码
class_rw_t *data() {
return bits.data();
}
验证了类的相关信息都存在bits
里面,并且通过bits.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;
...省略其他的信息...
};
我们似乎看到了我们熟悉的属性,方法,代理等相关的数据类型。 但是这里要注意了,我们类的属性,方法并没有存在
method_array_t
、property_array_t
这些类型的属性里面(它是什么,后面章节会介绍)。 而是存放在了class_ro_t
这个结构体里面,我们看到定义的是const
,可以说明这一块会在编译时候就确定好了,后面取出来使用是不可以更改的。
- 观察
class_ro_t
结构体
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize;
#ifdef __LP64__
uint32_t reserved;
#endif
const uint8_t * ivarLayout;
const char * name;
method_list_t * baseMethodList;
protocol_list_t * baseProtocols;
const ivar_list_t * ivars;
const uint8_t * weakIvarLayout;
property_list_t *baseProperties;
...省略其他的信息...
- 成员变量存放
const ivar_list_t * ivars
。- 属性存放
property_list_t *baseProperties
。- 方法存放
method_list_t * baseMethodList
。
5. 属性-方法的归属探索
5.1 准备条件
定义成员变量、属性、对象方法、类方法。
@interface XDPerson : NSObject
{
NSString *otherName;
}
@property (nonatomic, copy) NSString *nickName;
- (void)sayHello;
+ (void)sayHappy;
@end
5.2 lldb
调试
在文章的开始已经了解了在连续内存段里面可以通过指针偏移找到对应的内存段。
- 查看类
XDPerson
的内存信息
(lldb) x/4gx XDPerson.class
0x100001318: 0x001d8001000012f1 0x0000000100aff140
0x100001328: 0x000000010203ce00 0x0000000200000003
- 我们知道信息在
objc_class
结构体的bits
属性里面,通过内存地址偏移来找到bits
的内存地址,我们直接去找0x100001318
+32个字节=0x100001338
(lldb) p (class_data_bits_t *)0x100001338
(class_data_bits_t *) $1 = 0x0000000100001338
- 寻找
class_rw_t
通过bits.data()
(lldb) p $1->data()
(class_rw_t *) $2 = 0x000000010203cd50
- 查看
class_rw_t
内存数据信息
(lldb) p *$2
(class_rw_t) $3 = {
flags = 2148139008
version = 0
ro = 0x00000001000011f0
methods = {
list_array_tt<method_t, method_list_t> = {
= {
list = 0x0000000100001128
arrayAndFlag = 4294971688
}
}
}
properties = {
list_array_tt<property_t, property_list_t> = {
= {
list = 0x00000001000011d8
arrayAndFlag = 4294971864
}
}
}
protocols = {
list_array_tt<unsigned long, protocol_list_t> = {
= {
list = 0x0000000000000000
arrayAndFlag = 0
}
}
}
firstSubclass = nil
nextSiblingClass = NSDate
demangledName = 0x0000000000000000 <no value available>
}
- 查看目标
ro
(lldb) p $3.ro
(const class_ro_t *) $4 = 0x00000001000011f0
- 查看
class_ro_t
的内存数据信息
(lldb) p *$4
(const class_ro_t) $5 = {
flags = 388
instanceStart = 8
instanceSize = 24
reserved = 0
ivarLayout = 0x0000000100000f80 "\x02"
name = 0x0000000100000f77 "XDPerson"
baseMethodList = 0x0000000100001128
baseProtocols = 0x0000000000000000
ivars = 0x0000000100001190
weakIvarLayout = 0x0000000000000000 <no value available>
baseProperties = 0x00000001000011d8
_swiftMetadataInitializer_NEVER_USE = {}
}
- 查看
baseProperties
(lldb) p $5.baseProperties
(property_list_t *const) $6 = 0x00000001000011d8
(lldb) p *$6
(property_list_t) $7 = {
entsize_list_tt<property_t, property_list_t, 0> = {
entsizeAndFlags = 16
count = 1
first = (name = "nickName", attributes = "T@"NSString",C,N,V_nickName")
}
}
有且仅有一个元素
nickName
和我们定义的相同。
- 查看
ivars
(lldb) p $5.ivars
(const ivar_list_t *const) $8 = 0x0000000100001190
(lldb) p *$8
(const ivar_list_t) $9 = {
entsize_list_tt<ivar_t, ivar_list_t, 0> = {
entsizeAndFlags = 32
count = 2
first = {
offset = 0x00000001000012e8
name = 0x0000000100000f20 "otherName"
type = 0x0000000100000fa7 "@"NSString""
alignment_raw = 3
size = 8
}
}
}
(lldb) p $9.get(0)
(ivar_t) $10 = {
offset = 0x00000001000012e8
name = 0x0000000100000f20 "otherName"
type = 0x0000000100000fa7 "@"NSString""
alignment_raw = 3
size = 8
}
(lldb) p $9.get(1)
(ivar_t) $11 = {
offset = 0x00000001000012e0
name = 0x0000000100000f2a "_nickName"
type = 0x0000000100000fa7 "@"NSString""
alignment_raw = 3
size = 8
}
有两个元素,一个是成员变量
otherName
,另一个是编译生成的成员变量_nickName
。
- 查看
baseMothedList
(lldb) p $5.baseMethodList
(method_list_t *const) $12 = 0x0000000100001128
(lldb) p *$12
(method_list_t) $13 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 26
count = 4
first = {
name = "sayHello"
types = 0x0000000100000f8c "v16@0:8"
imp = 0x0000000100000d70 (XDTest`-[XDPerson sayHello] at XDPerson.m:11)
}
}
}
(lldb) p $13.get(0)
(method_t) $14 = {
name = "sayHello"
types = 0x0000000100000f8c "v16@0:8"
imp = 0x0000000100000d70 (XDTest`-[XDPerson sayHello] at XDPerson.m:11)
}
(lldb) p $13.get(1)
(method_t) $15 = {
name = "nickName"
types = 0x0000000100000f94 "@16@0:8"
imp = 0x0000000100000d90 (XDTest`-[XDPerson nickName] at XDPerson.h:16)
}
(lldb) p $13.get(2)
(method_t) $16 = {
name = "setNickName:"
types = 0x0000000100000f9c "v24@0:8@16"
imp = 0x0000000100000dc0 (XDTest`-[XDPerson setNickName:] at XDPerson.h:16)
}
(lldb) p $13.get(3)
(method_t) $17 = {
name = ".cxx_destruct"
types = 0x0000000100000f8c "v16@0:8"
imp = 0x0000000100000e00 (XDTest`-[XDPerson .cxx_destruct] at XDPerson.m:10)
}
sayHello
定义的对象方法。nickName
底层生成的getter
方法。setNickName:
底层生成的setter
方法。.cxx_destruct
系统c++
的析构函数。
这里也验证了属性在底层生成了 ivar
+getter
+setter
。
sayHappy
类方法去哪了?
通过iOS 底层探索篇 —— isa的初始化&指向分析这篇文章的分析,我们可以了解到类在元类那里也相当于是一个对象,那类的类方法会不会在元类那里也类似一个对象方法给存起来了?
...省略部分调试 去查找元类的相关信息...
(lldb) p $25.baseMethodList
(method_list_t *const) $26 = 0x00000001000010c0
(lldb) p *$26
(method_list_t) $27 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 26
count = 1
first = {
name = "sayHappy"
types = 0x0000000100000f8c "v16@0:8"
imp = 0x0000000100000d80 (XDTest`+[XDPerson sayHappy] at XDPerson.m:15)
}
}
}
sayHappy
类方法在元类里面找到了。
验证了我们的成员遍历,属性,方法都在class_ro_t
的这个机构体里面找到了。
其实笔者也去找了一下协议,但是很可惜没有找到,后面会在单独研究协议,原谅能力有限,不足之处还请指出。
其他结构体分析
在上面的分析中我们看到了property_t
、ivar_t
、method_t
这三个结构体。接下来我们做一下简单的分析。
1. property_t
struct property_t {
const char *name;
const char *attributes;
};
name
属性的名字;attributes
描述字段;
2. ivar_t
struct ivar_t {
#if __x86_64__
// *offset was originally 64-bit on some x86_64 platforms.
// We read and write only 32 bits of it.
// Some metadata provides all 64 bits. This is harmless for unsigned
// little-endian values.
// Some code uses all 64 bits. class_addIvar() over-allocates the
// offset for their benefit.
#endif
int32_t *offset;
const char *name;
const char *type;
// alignment is sometimes -1; use alignment() instead
uint32_t alignment_raw;
uint32_t size;
uint32_t alignment() const {
if (alignment_raw == ~(uint32_t)0) return 1U << WORD_SHIFT;
return 1 << alignment_raw;
}
};
offset
偏移内存地址;name
名字;type
类型;alignment_raw
对齐信息;size
所占字节;
3. method_t
struct method_t {
SEL name;
const char *types;
MethodListIMP imp; //using MethodListIMP = IMP;
struct SortBySELAddress :
public std::binary_function<const method_t&,
const method_t&, bool>
{
bool operator() (const method_t& lhs,
const method_t& rhs)
{ return lhs.name < rhs.name; }
};
};
name
方法名;types
方法前面;imp
方法实现;
学习之路,砥砺前行
不足之处可以在评论区指出