一、前置知识
CPU 访问内存时需要的是地址,而不是变量名和函数名!变量名和函数名只是地址的一种助记符,当源文件被编译和链接成可执行程序后,它们都会被替换成地址。编译和链接过程的一项重要任务就是找到这些名称所对应的地址。
1.1 C语言指针变量的运算(加法、减法和比较运算)
指针 变量保存的是 地址,而地址本质上是一个整数,所以指针变量可以进行部分运算,例如加法、减法、比较等,请看下面的代码:
int a = 10, *pa = &a, *paa = &a;
double b = 99.9, *pb = &b;
char c = '@', *pc = &c;
//最初的值
NSLog(@"最初的值:");
NSLog(@"&a=%p, &b=%p, &c=%p \n", &a, &b, &c);
NSLog(@"pa=%p, pb=%p, pc=%p \n", pa, pb, pc);
//加法运算
pa++; pb++; pc++;
NSLog(@"各自+1后:");
NSLog(@"pa=%p, pb=%p, pc=%p \n", pa, pb, pc);
//减法运算
NSLog(@"然后-2后:");
pa -= 2; pb -= 2; pc -= 2;
NSLog(@"pa=%p, pb=%p, pc=%p \n", pa, pb, pc);
//比较运算
if(pa == paa){
NSLog(@"pa和paa相等: %d\n", *paa);
}else{
NSLog(@"pa和paa不相等: %d\n", *pa);
}
最初的值:
&a=0x7ffeed1bbd80, &b=0x7ffeed1bbd78, &c=0x7ffeed1bbd87
pa=0x7ffeed1bbd80, pb=0x7ffeed1bbd78, pc=0x7ffeed1bbd87
各自+1后:
pa=0x7ffeed1bbd84, pb=0x7ffeed1bbd80, pc=0x7ffeed1bbd88
然后-2后:
pa=0x7ffeed1bbd7c, pb=0x7ffeed1bbd70, pc=0x7ffeed1bbd86
pa和paa不相等: 1079572889
从运算结果可以看出:pa
、pb
、pc
每次加 1,它们的地址分别增加 4、8、1,正好是 int
、double
、char
类型的长度;减 2 时,地址分别减少 8、16、2,正好是 int
、double
、char
类型长度的 2 倍。
这很奇怪,指针变量加减运算的结果跟数据类型的长度有关,而不是简单地加 1 或减 1,这是为什么呢?
以 a
和 pa
为例,a
的类型为int
,占用 4 个字节,pa
是指向 a
的指针,如下图所示:
刚开始的时候,pa
指向 a
的开头,通过 *pa
读取数据时,从 pa
指向的位置向后移动 4 个字节,把这 4 个字节的内容作为要获取的数据,这 4 个字节也正好是变量 a
占用的内存。
如果 pa++
;使得地址加 1 的话,就会变成如下图所示的指向关系:
这个时候 pa
指向整数 a
的中间,*pa
使用的是红色虚线画出的 4 个字节,其中前 3 个是变量 a
的,后面 1 个是其它数据的,把它们“搅和”在一起显然没有实际的意义,取得的数据也会非常怪异。
如果pa++
;使得地址加 4 的话,正好能够完全跳过整数 a
,指向它后面的内存,如下图所示:
总结:指针做 + 、-
运算的时候,是按指向内容类型的倍数运算。
二、Class (进入正题)
上篇文章分析isa的时候,遇到了 Class
,Class
是什么,内部又是怎么定义的?
源码用的还是objc4-787.1版本。
2.1 源码查看
typedef struct objc_class *Class;
继续查看 objc_class
,这个时候发现有3个 objc_class
的定义,都是 struct
,但是注意一个是 runtime.h
中的,会发现 已经标记 OBJC2_UNAVAILABLE
(OC2不可用了)。一个是 objc-runtime-old.h
(旧的不管)中的,还有个 objc-runtime-new.h
中的,跟进 objc-runtime-new.h
中的代码:
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);
}
// 中间省略很多方法 ...
bool isMetaClass() {
ASSERT(this);
ASSERT(isRealized());
#if FAST_CACHE_META
return cache.getBit(FAST_CACHE_META);
#else
return data()->flags & RW_META;
#endif
}
// Like isMetaClass, but also valid on un-realized classes
bool isMetaClassMaybeUnrealized() {
static_assert(offsetof(class_rw_t, flags) == offsetof(class_ro_t, flags), "flags alias");
static_assert(RO_META == RW_META, "flags alias");
return data()->flags & RW_META;
}
// NOT identical to this->ISA when this is a metaclass
Class getMeta() {
if (isMetaClass()) return (Class)this;
else return this->ISA();
}
bool isRootClass() {
return superclass == nil;
}
bool isRootMetaclass() {
return ISA() == (Class)this;
}
// 省略 ...
// 以前分析alloc的时候,熟悉的instanceSize
uint32_t alignedInstanceStart() const {
return word_align(unalignedInstanceStart());
}
// May be unaligned depending on class's ivars.
uint32_t unalignedInstanceSize() const {
ASSERT(isRealized());
return data()->ro()->instanceSize;
}
// Class's ivar size rounded up to a pointer-size boundary.
uint32_t alignedInstanceSize() const {
return word_align(unalignedInstanceSize());
}
size_t instanceSize(size_t extraBytes) const {
if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
return cache.fastInstanceSize(extraBytes);
}
size_t size = alignedInstanceSize() + extraBytes;
// CF requires all objects be at least 16 bytes.
if (size < 16) size = 16;
return size;
}
void setInstanceSize(uint32_t newSize) {
ASSERT(isRealized());
ASSERT(data()->flags & RW_REALIZING);
auto ro = data()->ro();
if (newSize != ro->instanceSize) {
ASSERT(data()->flags & RW_COPIED_RO);
*const_cast<uint32_t *>(&ro->instanceSize) = newSize;
}
cache.setFastInstanceSize(newSize);
}
}
发现 objc_class
继承与 objc_object
,继续跟进 objc_object
struct objc_object {
private:
isa_t isa;
public:
// ISA() assumes this is NOT a tagged pointer object
Class ISA();
// rawISA() assumes this is NOT a tagged pointer object or a non pointer ISA
Class rawISA();
// getIsa() allows this to be a tagged pointer object
Class getIsa();
uintptr_t isaBits() const;
// 省略很多方法 方法 方法 ...
}
发现 objc_object
只有一个私有属性 isa_t isa
;
2.2 验证经典isa流程图
这个isa流程图,见过好多次了,明白其流程,但是不知其怎么来的,
在分析 alloc
的时候,会初始化 instance
的 isa
赋值该对象的 Class
。
根据源码可知:
- 只要继承与
NSObject
的对象都会有一个isa
-
isa
中最重要的就是shiftcls
,其实就是指向Class
GLPerson *supP = [[GLPerson alloc] init];
// GLStudent 继承于 GLPerson
GLStudent *subS = [[GLStudent alloc] init];
验证 subS
也是一样的流程。
2.3 类(对象)只存在一份
Class c1 = object_getClass(subS1);
Class c2 = object_getClass(subS2);
Class c3 = object_getClass(subS3);
Class c4 = object_getClass(subS4);
NSLog(@"c1:%p; c2:%p; c3:%p; c4:%p", c1, c2, c3, c4);
console: c1:0x1026ca820; c2:0x1026ca820; c3:0x1026ca820; c4:0x1026ca820
三、objc_class
对象初始化的时候,好像没有要初始化属性、方法吧,那属性、方法、协议这些东西是放在那里了。
找了下 objc-runtime-old.h
(旧定义文件) 里面的定义,发现 objc_class
里面直接是有 ivars
methodLists
protocols
这些信息的。
而在 objc-runtime-new.h
(新定义文件)里面这些属性没了,而多了一个 class_data_bits_t bits
属性。
class_data_bits_t
源码:
struct class_data_bits_t {
friend objc_class;
// Values are the FAST_ flags above.
uintptr_t bits;
public:
class_rw_t* data() const {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
// 省略一些方法 ...
}
class_data_bits_t
里面有个方法 class_rw_t* data()
,会返回 class_rw_t
的数据。
继续查看 class_rw_t
struct 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};
}
}
}
可在里面找到 method_array_t
、property_array_t
、protocol_array_t
这些方法。从字面意思应该能看出这就是我们要找的东西了。
3.1 获取 class_data_bits_t
运行环境是在配置好可运行的 objc
源码的工程。
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
// 省略...
}
通过 objc_class
的源码可知,bits
存在第4个成员。只要计算出前面3个成员的大小,就可以通过内存偏移得到 bits
。isa
根据以前文章的分析可知是8字节。Class superclass
是一个指针类型,所以也是8字节。看下 cache_t
源码:
struct cache_t {
// typedef uint32_t mask_t;
// typedef unsigned int uint32_t; 所以mask_t类型是4字节
// typedef unsigned long uintptr_t; 所以 uintptr_t 类型是8字节
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
explicit_atomic<struct bucket_t *> _buckets; // 指针8字节
explicit_atomic<mask_t> _mask; // 4字节
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
explicit_atomic<uintptr_t> _maskAndBuckets; // long 8字节
mask_t _mask_unused; // 4字节
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
explicit_atomic<uintptr_t> _maskAndBuckets; // long 8字节
mask_t _mask_unused; // 4字节
#else
#error Unknown cache mask storage type.
#endif
// typedef unsigned short uint16_t; 这是uint16_t的定义
#if __LP64__
uint16_t _flags; // 2字节
#endif
uint16_t _occupied; // 2字节
// 省略很多方法和静态变量...
}
cache_t
源码总结:不管第一个if else
走哪个,加上下面2
个变量,总共占用16
字节。
计算偏移量:
-
继承的
isa
: 8字节; -
Class superclass
: 8字节; -
cache_t cache
:16字节;
所以,要想获取 bits
,总共需要在类的首地址上偏移32字节。
(lldb) x/4gx GLPerson.class
0x100002620: 0x00000001000025f8 0x0000000100334140
0x100002630: 0x00000001006a8540 0x0001802400000003
// 在GLPerson类首地址0x100002620的基础上偏移32字节。记得是16进制哦
(lldb) p (class_data_bits_t *)0x100002640
(class_data_bits_t *) $4 = 0x0000000100002640
3.2 打印 class_rw_t 中存储
@protocol GLPersonWalkProtocol <NSObject>
- (void)walk;
@end
@interface GLPerson : NSObject <GLPersonWalkProtocol>
@property (nonatomic, strong) NSString *name; /**< 8个字节 */
@property (nonatomic, strong) NSString *nickName; /**< 8个字节 */
- (void)sayHello;
+ (void)goSchool;
@end
@interface GLStudent : GLPerson
@end
---
GLPerson *supP = [[GLPerson alloc] init];
GLStudent *subS = [[GLStudent alloc] init];
通过 x
、p
、 po
命令打印
lldb) x/4gx supP
0x1006a4730: 0x001d800100002625 0x0000000000000000
0x1006a4740: 0x0000000000000000 0x0000000000000000
(lldb) po 0x001d800100002625 & 0x00007ffffffffff8ULL
GLPerson
(lldb) p 0x001d800100002625 & 0x00007ffffffffff8ULL
(unsigned long long) $2 = 4294977056 // 这是GLPerson类
(lldb) x/4gx $2 // 打印GLPerson类的地址和内存
0x100002620: 0x00000001000025f8 0x0000000100334140
0x100002630: 0x00000001006a8540 0x0001802400000003
// 另一种方式打印GLPerson类的地址和内存,再次验证是一样的
(lldb) x/4gx GLPerson.class
0x100002620: 0x00000001000025f8 0x0000000100334140
0x100002630: 0x00000001006a8540 0x0001802400000003
// 通过类的首地址偏移32字节得到$4,$4即是class_data_bits_t的首地址
(lldb) p (class_data_bits_t *)0x100002640
(class_data_bits_t *) $4 = 0x0000000100002640
// 调用class_data_bits_t的方法的到class_rw_t数据
(lldb) p *$4->data()
(class_rw_t) $5 = {
flags = 2148007936
witness = 1
ro_or_rw_ext = {
std::__1::atomic<unsigned long> = 4294976416
}
firstSubclass = GLStudent
nextSiblingClass = NSUUID
}
总结流程:
- 打印
GLPerson.class
的首地址和内存; - 通过地址偏移获取
class_data_bits_t *
指针赋值给$4
变量; - 通过
*$4
取地址里面的值可得到class_data_bits_t
; - 然后调用
data()
方法,返回class_rw_t
数据; - 通过
p
打印class_rw_t
;
3.3 获取 method_array_t
(lldb) x/4gx GLPerson.class
0x1000022b8: 0x0000000100002290 0x0000000100333140
0x1000022c8: 0x0000000100709790 0x0001802400000003
(lldb) p (class_data_bits_t *)0x1000022d8
(class_data_bits_t *) $2 = 0x00000001000022d8
(lldb) p *$2->data()
(class_rw_t) $3 = {
flags = 2148007936
witness = 1
ro_or_rw_ext = {
std::__1::atomic<unsigned long> = 4294975632
}
firstSubclass = GLStudent
nextSiblingClass = NSUUID
}
(lldb) p $3.methods()
(const method_array_t) $4 = {
list_array_tt<method_t, method_list_t> = {
= {
list = 0x00000001000020d8
arrayAndFlag = 4294975704
}
}
}
(lldb) p $4.list
(method_list_t *const) $5 = 0x00000001000020d8
(lldb) p *$5
(method_list_t) $6 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 26
count = 6
first = {
name = "sayHello"
types = 0x0000000100000f7c "v16@0:8"
imp = 0x0000000100000d80 (`-[GLPerson sayHello])
}
}
}
(lldb) p $6.get(0)
(method_t) $7 = {
name = "sayHello"
types = 0x0000000100000f7c "v16@0:8"
imp = 0x0000000100000d80 (`-[GLPerson sayHello])
}
(lldb) p $6.get(1)
(method_t) $8 = {
name = ".cxx_destruct"
types = 0x0000000100000f7c "v16@0:8"
imp = 0x0000000100000e30 (`-[GLPerson .cxx_destruct])
}
(lldb) p $6.get(2)
(method_t) $9 = {
name = "name"
types = 0x0000000100000f90 "@16@0:8"
imp = 0x0000000100000d90 (`-[GLPerson name])
}
(lldb) p $6.get(3)
(method_t) $10 = {
name = "setName:"
types = 0x0000000100000f98 "v24@0:8@16"
imp = 0x0000000100000db0 (`-[GLPerson setName:])
}
(lldb) p $6.get(4)
(method_t) $11 = {
name = "setNickName:"
types = 0x0000000100000f98 "v24@0:8@16"
imp = 0x0000000100000e00 (`-[GLPerson setNickName:])
}
(lldb) p $6.get(5)
(method_t) $12 = {
name = "nickName"
types = 0x0000000100000f90 "@16@0:8"
imp = 0x0000000100000de0 (`-[GLPerson nickName])
}
(lldb) p $6.get(6)
Assertion failed: (i < count), function get, file /Users/xulong/Desktop/学习/源码/alloc流程分析/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.
总结流程:
- 通过
*$2->data()
获取到(class_rw_t) $3
; - 调用
(class_rw_t) $3
的methods
方法获取(const method_array_t) $4
; - 调用
$4.list
方法 获取指向method_list_t
的指针(method_list_t *const) $5
; -
p *$5
取$5
指向的存储内容,得到(method_list_t) $6
; -
method_list_t
继承于entsize_list_tt
,entsize_list_tt
有个方法Element& get(uint32_t i)
,可根据第一个item偏移地址获取要取的item。 - 通过
get(0)、get(1) ...
获取方法列表知道越界。
可以发现方法列表里有我们定义的实例方法,却没有类方法 + (void)goSchool;
。
3.4 获取 property_array_t
同样方式也可打印出 const property_array_t properties()
。
(lldb) x/4gx GLPerson.class
0x1000022b8: 0x0000000100002290 0x0000000100333140
0x1000022c8: 0x0000000100709790 0x0001802400000003
(lldb) p (class_data_bits_t *)0x1000022d8
(class_data_bits_t *) $2 = 0x00000001000022d8
(lldb) p *$2->data()
(class_rw_t) $3 = {
flags = 2148007936
witness = 1
ro_or_rw_ext = {
std::__1::atomic<unsigned long> = 4294975632
}
firstSubclass = GLStudent
nextSiblingClass = NSUUID
}
(lldb) p $3.properties()
(const property_array_t) $13 = {
list_array_tt<property_t, property_list_t> = {
= {
list = 0x00000001000021b8
arrayAndFlag = 4294975928
}
}
}
(lldb) p $13.list
(property_list_t *const) $14 = 0x00000001000021b8
(lldb) p *$14
(property_list_t) $15 = {
entsize_list_tt<property_t, property_list_t, 0> = {
entsizeAndFlags = 16
count = 2
first = (name = "name", attributes = "T@\"NSString\",&,N,V_name")
}
}
(lldb) p $15.get(0)
(property_t) $16 = (name = "name", attributes = "T@\"NSString\",&,N,V_name")
(lldb) p $15.get(1)
(property_t) $17 = (name = "nickName", attributes = "T@\"NSString\",&,N,V_nickName")
(lldb) p $15.get(2)
Assertion failed: (i < count), function get, file /Users/xulong/Desktop/学习/源码/alloc流程分析/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.
继续探索:类结构下篇