对象的本质和isa

对象的本质和isa

将OC文件编译成 C++ 文件

使用 clang 命令,将OC文件编译成 C++ 文件

1
clang -rewrite-objc main.m -o main.cpp

如遇到 UIKit 错误,需增加如下参数

1
clang -rewrite-objc -fobjc-arc -fobjc-runtime=ios-14.0.0 -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator14.4.sdk main.m 
  • -fobjc-runtime:指定目标Objective-C运行时类型和版本
  • -isysroot:指定sdk路径

使用xcrun命令

1
2
3
4
# 模拟器版本
xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp
# 真机版本
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp

编译示例

编译前代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#import <Foundation/Foundation.h>
#import <objc/runtime.h>

@interface Person : NSObject
@property (nonatomic, strong) NSString *Name;
@end

@implementation Person
@end

int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"Hello, World!");
}
return 0;
}

编译后代码

1
2
3
4
5
6
typedef struct objc_object Person;

struct Person_IMPL {
struct NSObject_IMPL NSObject_IVARS;
NSString *_Name;
};

找到NSObject_IMPL的实现

1
2
3
struct NSObject_IMPL {
Class isa;
};

NSObject 包含成员变量 isa

Class 的定义和实现

1
2
3
4
5
typedef struct objc_class *Class;

struct objc_class {
Class _Nonnull isa __attribute__((deprecated));
} __attribute__((unavailable));

Class为objc_class结构体指针,占8字节

objc_object的实现

1
2
3
struct objc_object {
Class _Nonnull isa __attribute__((deprecated));
};

属性的getter/setter方法

1
2
3
static NSString * _I_Person_Name(Person * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_Person$_Name)); }

static void _I_Person_setName_(Person * self, SEL _cmd, NSString *Name) { (*(NSString **)((char *)self + OBJC_IVAR_$_Person$_Name)) = Name; }

通过内存平移进行读取和写入

1
(*(NSString **)((char *)self + OBJC_IVAR_$_Person$_Name))
  • self为结构体首地址
  • OBJC_IVAR_$_Person$_Name为属性的偏移地址

id类型

1
typedef struct objc_object *id;

id 类型是 objc_object *,所以定义 id 不用加*

小结

  • 对象的本质是结构体
  • 类也是对象,本质同样是结构体
  • OC中对象继承于NSObject,底层是objc_object结构体
  • isa是结构体指针,占8字节

联合体位域

信息的存取一般以字节为单位。实际上,有时存储一个信息不必用一个或多个字节,例如:“真/假”值,用0或1表示,只需1位即可。
联合体共用的存储空间大小是以最大的数据类型为准。
位域(Bit field):为一种数据结构,可以把数据以位的形式紧凑的储存,并允许程序员对此结构的位进行操作

使用位域的优势:
可以使数据单元节省储存空间,当程序需要成千上万个数据单元时,这种方法就显得尤为重要
位域可以很方便的访问一个整数值的部分内容从而可以简化程序源代码

使用联合体位域优化内存

未使用联合体位域

1
2
3
4
5
6
7
8
9
struct Car1 {
BOOL front;
BOOL back;
BOOL left;
BOOL right;
};

struct Car1 car1;
NSLog(@"%ld",sizeof(car1)); // 4

占用4个字节,即32位

使用联合体位域

1
2
3
4
5
6
7
8
// : 后为所需的位数
struct Car2 {
BOOL front : 1;
BOOL back : 1;
BOOL left : 1;
BOOL right : 1;
};
NSLog(@"%ld",sizeof(car2)); // 1

只占用4位(8位为1字节),但是最小单位是1字节,所以为1字节

isa 关联类信息

在 objc4 源码 initInstanceIsa 中,会将 class 与 isa 关联

1
2
3
4
5
6
7
inline void 
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor) {
ASSERT(!cls->instancesRequireRawIsa());
ASSERT(hasCxxDtor == cls->hasCxxDtor());

initIsa(cls, true, hasCxxDtor);
}

进入initIsa源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
inline void 
objc_object::initIsa(Class cls, bool nonpointer, >UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT bool hasCxxDtor) {
ASSERT(!isTaggedPointer());

isa_t newisa(0);

if (!nonpointer) {
newisa.setClass(cls, this);
} else {
ASSERT(!DisableNonpointerIsa);
ASSERT(!cls->instancesRequireRawIsa());

#if SUPPORT_INDEXED_ISA
ASSERT(cls->classArrayIndex() > 0);
newisa.bits = ISA_INDEX_MAGIC_VALUE;

newisa.has_cxx_dtor = hasCxxDtor;
newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
newisa.bits = ISA_MAGIC_VALUE;

# if ISA_HAS_CXX_DTOR_BIT
newisa.has_cxx_dtor = hasCxxDtor;
# endif
newisa.setClass(cls, this);
#endif
newisa.extra_rc = 1;
}

isa = newisa;
}
  • 初始化isa_t
  • 非NonpointerIsa(表示是否对 isa 指针开启指针优化 0:纯isa指针,1:不止是类对象地址,isa 中包含了类信息、对象的引用计数等),直接设置class
  • 否则,对bits赋值,关联类信息

isa_t 定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }

uintptr_t bits;

private:
Class cls;

public:
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD;
};

bool isDeallocating() {
return extra_rc == 0 && has_sidetable_rc == 0;
}
void setDeallocating() {
extra_rc = 0;
has_sidetable_rc = 0;
}
#endif

void setClass(Class cls, objc_object *obj);
Class getClass(bool authenticated);
Class getDecodedClass(bool authenticated);
};
  • isa_t为联合体
  • 有bits和cls两个成员变量,它们是互斥的

ISA_BITFIELD 的定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# if __arm64__
# if __has_feature(ptrauth_calls) || TARGET_OS_SIMULATOR
...
# else
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
# define ISA_HAS_CXX_DTOR_BIT 1
# define ISA_BITFIELD \
uintptr_t nonpointer : 1; \
uintptr_t has_assoc : 1; \
uintptr_t has_cxx_dtor : 1; \
uintptr_t shiftcls : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
uintptr_t magic : 6; \
uintptr_t weakly_referenced : 1; \
uintptr_t unused : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 19
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
# endif

# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
# define ISA_MAGIC_MASK 0x001f800000000001ULL
# define ISA_MAGIC_VALUE 0x001d800000000001ULL
# define ISA_HAS_CXX_DTOR_BIT 1
# define ISA_BITFIELD \
uintptr_t nonpointer : 1; \
uintptr_t has_assoc : 1; \
uintptr_t has_cxx_dtor : 1; \
uintptr_t shiftcls : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \
uintptr_t magic : 6; \
uintptr_t weakly_referenced : 1; \
uintptr_t unused : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 8
# define RC_ONE (1ULL<<56)
# define RC_HALF (1ULL<<7)

# else
# error unknown architecture for packed isa
# endif
  • nonpointer:表示是否对isa指针开启指针优化。0:纯isa指针,1:不⽌是类对象地址,isa中包含了类信息、对象的引⽤计数等
  • has_assoc:关联对象标志位。0:不存在,1:存在
  • has_cxx_dtor:该对象是否有C++或者Objc的析构器。如果有析构函数,则需要做析构逻辑。如果没有,则可以更快的释放对象
  • shiftcls:存储类指针的值。开启指针优化的情况下,在arm64架构中,有 33位⽤来存储类指针
  • magic:⽤于调试器判断当前对象是真的对象还是没有初始化的空间
  • weakly_referenced:标志对象是否被指向或者曾经指向⼀个ARC的弱变量,没有弱引⽤的对象可以更快释放
  • deallocating:标志对象是否正在释放内存
  • has_sidetable_rc:当对象引⽤计数⼤于10时,则需要借⽤该变量存储进位
  • extra_rc:表示该对象的引⽤计数值,实际上是引⽤计数值减1。例如,如果对象的引⽤计数为10,那么extra_rc为9。如果引⽤计数⼤于 10,则需要使⽤到下⾯的has_sidetable_rc

ISA_MASK
ISA_MASK:宏定义,不同CPU架构下的值不一样。nonpointer类型isa,需要isa和ISA_MASK进行&(与运算),才能得到类对象地址

isa 通过ISA_MASK转地址

先创建一个对象,并打断点

1
Person *person= [Person alloc];

打印内存数据 (打印4组 每组8字节)

1
2
3
4
x/4g person
-------------------------
0x10077b200: 0x011d80010000832d 0x0000000000000000
0x10077b210: 0x0000000000000000 0x0000000000000000

isa: 0x011d80010000832d

通过和ISA_MASK进行&(与运算),得到类对象地址

1
2
3
p/x 0x011d800100008335 & 0x00007ffffffffff8ULL
-------------------------
0x0000000100008330

验证一下,和person.class 打印结果一致

1
2
3
p/x per.class
-------------------------
0x0000000100008330 LGPerson

ISA_MASK 的本质

1
2
3
4
# 以二进制打印
p/t 0x00007ffffffffff8ULL
-------------------------
0b0000000000000000011111111111111111111111111111111111111111111000

在x86_64架构下,ISA_MASK的高17位和低3位为0,中间44位为1。也就是说,只显示isa中的shiftcls部分。即:存储类指针的值

isa 通过位运算转地址

在x86_64架构中,shiftcls存储在3~47位。即使我们不知道ISA_MASK的存在,直接通过isa平移,同样可以得到类对象地址

1
2
3
4
5
6
7
# 0x011d80010000832d 为isa

p/x 0x011d800100008335 >> 3
p/x 0x0023b00020001066 << 20
p/x 0x0002000106600000 >> 17
-------------------------
0x0000000100008330 Person

isa的指向

直接放官方图,简洁明了
img

使用lldb验证

// 新建一个类并打断点 YCPerson 继承自 NSObject

1
YCPerson *p = [YCPerson alloc];

在lldb中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# 首先打印p的内存
(lldb) x/4gx p
0x100627bc0: 0x011d800100008195 0x0000000000000000 # 0x011d800100008195 是 isa
0x100627bd0: 0x000000010062c8c0 0xbd0d0d3c1be364de

# 实例对象isa & ISA_MASK 获得YCPerson类对象地址
(lldb) p/x 0x011d800100008195 & 0x00007ffffffffff8ULL
(unsigned long long) $3 = 0x0000000100008190
(lldb) po 0x0000000100008190
YCPersen

# 打印类对象内存空间
(lldb) x/4gx 0x0000000100008190
0x100008190: 0x0000000100008168 0x000000010036e140
0x1000081a0: 0x00000001003663a0 0x0000801c00000000

# 打印类对象isa & ISA_MASK => 元类对象地址
(lldb) p/x 0x0000000100008168 & 0x00007ffffffffff8ULL
(unsigned long long) $7 = 0x0000000100008168
(lldb) po 0x0000000100008168
YCPersen

# 打印元类对象内存空间
(lldb) x/4gx 0x0000000100008168
0x100008168: 0x000000010036e0f0 0x000000010036e0f0
0x100008178: 0x00000001012112d0 0x0002e03500000003

# 元类对象isa & ISA_MASK
(lldb) p/x 0x00007ffffffffff8ULL & 0x000000010036e0f0
(unsigned long long) $10 = 0x000000010036e0f0
(lldb) po 0x000000010036e0f0
NSObject

# 0x000000010036e0f0 是根元类的地址,根元类的地址 & ISA_MASK 不变

用代码验证

1
2
3
4
5
6
7
8
9
10
11
YCPerson *p = [YCPerson alloc];
Class clsP = object_getClass(p);
Class mclsP = object_getClass(clsP);
Class rmclsP = object_getClass(mclsP);
NSLog(@"%@ - %p", clsP, &clsP);
NSLog(@"%@ - %p", mclsP, &mclsP);
NSLog(@"%@ - %p", rmclsP, &rmclsP);
====输出====
YCPersen - 0x7ffeefbff428
YCPersen - 0x7ffeefbff430
NSObject - 0x7ffeefbff420