1. 对象本质
1.1 clang
clang
是⼀个由Apple
主导编写,基于LLVM
的C/C++/Objective-C
的轻量级编译器。源代码发布于BSD
协议下。将⽀持其普通lambda
表达式、返回类型的简化处理以及更好的处理constexpr
关键字
2013
年4
⽉,clang
已经全⾯⽀持C++11
标准,并开始实现C++1y
特性(也就是C++14
,这是C++
的下⼀个⼩更新版本)。clang
将⽀持其普通lambda
表达式、返回类型的简化处理以及更好的处理constexpr
关键字
clang
与GNU C
语⾔规范⼏乎完全兼容(当然,也有部分不兼容的内容,包括编译命令选项也会有点差异),并在此基础上增加了额外的语法特性,⽐如C函数
重载(通过__attribute__((overloadable))
来修饰函数),其⽬标(之⼀)就是超越GCC
使用clang
命令,将⽬标⽂件编译成C++
⽂件
clang -rewrite-objc main.m -o main.cpp
遇到UIKit
错误
main.m:9:9: fatal error: 'UIKit/UIKit.h' file not found
使用以下参数:
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
路径
1.2 xcrun
安装Xcode
时,顺带安装了xcrun
命令。xcrun
命令在clang
的基础上进⾏了⼀些封装,要更好⽤⼀些
生成模拟器版本
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.3 探索对象本质
使用clang
还原Objective-C
代码在底层的实现
打开man.m
函数,写入以下代码:
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
@interface LGPerson : NSObject
@property (nonatomic, strong) NSString *Name;
@end
@implementation LGPerson
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"Hello, World!");
}
return 0;
}
使用clang
命令,生成main.cpp
文件
clang -rewrite-objc main.m -o main.cpp
通过cpp
文件,探索对象本质
找到LGPerson
的定义与实现
typedef struct objc_object LGPerson;
struct LGPerson_IMPL {
struct NSObject_IMPL NSObject_IVARS;
NSString *_Name;
};
LGPerson
定义为objc_object
类型LGPerson_IMPL
是LGPerson
的底层实现LGPerson_IMPL
中嵌套NSObject_IMPL
结构体- 结构体嵌套相当于伪继承
找到NSObject
的定义与实现
typedef struct objc_object NSObject;
struct NSObject_IMPL {
Class isa;
};
NSObject
定义为objc_object
类型NSObject_IMPL
是NSObject
的底层实现- 只有一个
Class
类型的成员变量isa
找到Class
的定义与实现
typedef struct objc_class *Class;
struct objc_class {
Class _Nonnull isa __attribute__((deprecated));
} __attribute__((unavailable));
Class
类型,本质是objc_class
结构体指针,占8字节
找到objc_object
的实现
struct objc_object {
Class _Nonnull isa __attribute__((deprecated));
};
- 只有一个
Class
类型的成员变量isa
- 所有对象的底层实现,本质上都来自于
objc_object
结构体
结论:
- 对象的本质是结构体
- 类也是对象,本质同样是结构体
- 万物皆对象,万物皆有
isa
isa
本质是结构体指针,占8字节
- 所有对象的底层实现,本质上都来自于
objc_object
结构体
1.4 属性的getter/setter
方法
static NSString * _I_LGPerson_Name(LGPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_Name)); }
static void _I_LGPerson_setName_(LGPerson * self, SEL _cmd, NSString *Name) { (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_Name)) = Name; }
- 包含
self
和_cmd
隐藏参数
(*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_Name))
- 通过内存平移进行读写操作
self
为结构体首地址OBJC_IVAR_$_LGPerson$_Name
为属性的偏移地址
1.5 id
类型
typedef struct objc_object *id;
id
为别名,本质是objc_object
结构体指针- 所以,在
OC
中,定义id
类型不用加*
- 由于所有对象的底层实现,本质上都来自于
objc_object
结构体,故此id
类型可以存储任意对象
2. 联合体 / 位域
2.1 位域
信息的存取一般以字节为单位。实际上,有时存储一个信息不必用一个或多个字节,例如:“真/假”值,用0
或1
表示,只需1位
即可
位域(Bit field
):为一种数据结构,可以把数据以位的形式紧凑的储存,并允许程序员对此结构的位进行操作
使用位域的优势:
- 可以使数据单元节省储存空间,当程序需要成千上万个数据单元时,这种方法就显得尤为重要
- 位域可以很方便的访问一个整数值的部分内容从而可以简化程序源代码
案例:
struct LGCar1 {
BOOL front;
BOOL back;
BOOL left;
BOOL right;
};
LGCar1
结构体中,包含前、后、左、右四个BOOL
类型的成员变量
struct LGCar1 car1;
NSLog(@"LGCar1:%ld",sizeof(car1));
-------------------------
LGCar1:4
- 占
4字节
,即32位
4个
“真/假”值,分配4字节
,实属空间浪费。正常来说,使用4位
就可表示,占1字节
即可
0000 1111
- 使用
低4位
,分别表示前、后、左、右
使用位域节省储存空间
struct LGCar2 {
BOOL front : 1;
BOOL back : 1;
BOOL left : 1;
BOOL right : 1;
};
- 语法:在成员变量后面增加
: x
,表示其所占位数
struct LGCar2 car2;
NSLog(@"LGCar2:%ld",sizeof(car2));
-------------------------
LGCar2:1
- 仅占
1字节
,4位
就够用
2.2 联合体
结构体(
struct
):所有变量是“共存”的
- 优点:包容性强,成员之间不会相互影响
- 缺点:内存空间的浪费,不管⽤不⽤,全分配
struct LGTeacher1 {
char *name;
int age;
};
- 定义结构体,包含
name
和age
两个成员变量
struct LGTeacher1 teacher1;
teacher1.name = "Cooci";
teacher1.age = 18;
NSLog(@"name:%s,age:%i,sizeof:%ld", teacher1.name, teacher1.age, sizeof(teacher1));
-------------------------
name:Cooci,age:18,sizeof:16
- 成员之间不会相互影响,占
16字节
联合体(
union
):成员之间内存共用,各变量是“互斥”的
- 优点:节省内存空间,内存的使⽤更为精细灵活
- 缺点:包容性较差
union LGTeacher2 {
char *name;
int age;
};
- 定义联合体,包含
name
和age
两个成员变量
struct LGTeacher1 teacher1;
teacher1.age = 18;
teacher1.name = "Cooci";
NSLog(@"name:%s,age:%i,sizeof:%ld", teacher1.name, teacher1.age, sizeof(teacher1));
-------------------------
name:Cooci,age:15934,sizeof:8
- 成员之间内存共用,只有一个成员变量有值。由于
name
赋值,导致age
变为坏地址 - 联合体的大小,为最大成员变量的大小
- 联合体一般会配合位域一起使用
2.3 项目中的使用
打开LGCar.m
文件,写入以下代码:
宏定义
#import "LGCar.h"
#define LGDirectionFrontMask (1 << 0)
#define LGDirectionBackMask (1 << 1)
#define LGDirectionLeftMask (1 << 2)
#define LGDirectionRightMask (1 << 3)
定义联合体 + 位域
@interface LGCar() {
union {
char bits;
struct {
char front : 1;
char back : 1;
char left : 1;
char right : 1;
};
} _direction;
}
@end
初始化
- (instancetype)init {
self = [super init];
if (self) {
_direction.bits = 0b0000000000;
}
return self;
}
front
的getter/setter
方法
- (void)setFront:(BOOL)isFront {
if (isFront) {
_direction.bits |= LGDirectionFrontMask;
} else {
_direction.bits |= ~LGDirectionFrontMask;
}
NSLog(@"%s",__func__);
}
- (BOOL)isFront{
return _direction.front;
}
3. isa关联类信息
initInstanceIsa
函数是alloc
的核心方法之一,负责将class
与isa
进行关联
3.1 探索objc
源码
3.1.1 initInstanceIsa
函数
inline void
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor) {
ASSERT(!cls->instancesRequireRawIsa());
ASSERT(hasCxxDtor == cls->hasCxxDtor());
initIsa(cls, true, hasCxxDtor);
}
3.1.2 initIsa
函数
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
- 非
nonpointer
,直接设置class
- 否则,对
bits
赋值,关联类信息
找到SUPPORT_INDEXED_ISA
的定义:
#if __ARM_ARCH_7K__ >= 2 || (__arm64__ && !__LP64__)
# define SUPPORT_INDEXED_ISA 1
#else
# define SUPPORT_INDEXED_ISA 0
#endif
- 条件判断,默认为
# define SUPPORT_INDEXED_ISA 1
的分支
3.1.3 isa_t
联合体
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
:isa
结构
3.1.4 isa
结构
# if __arm64__
# if __has_feature(ptrauth_calls) || TARGET_OS_SIMULATOR
# define ISA_MASK 0x007ffffffffffff8ULL
# define ISA_MAGIC_MASK 0x0000000000000001ULL
# define ISA_MAGIC_VALUE 0x0000000000000001ULL
# define ISA_HAS_CXX_DTOR_BIT 0
# define ISA_BITFIELD \
uintptr_t nonpointer : 1; \
uintptr_t has_assoc : 1; \
uintptr_t weakly_referenced : 1; \
uintptr_t shiftcls_and_sig : 52; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 8
# define RC_ONE (1ULL<<56)
# define RC_HALF (1ULL<<7)
# 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
- 不同
CPU
架构,isa
结构略有差异 __has_feature
:编译器是否支持该功能ptrauth_calls
:arm64e
架构,引入指针验证代码 (PAC
)
◦ 用于检测和防止内存中指针的意外更改
◦ 支持Apple A12
或更高版本A
系列处理器的设备(如:XS/XS Max/XR
)
◦ 详情可参见:官方文档
TARGET_OS_SIMULATOR
:判断是否为模拟器
3.2 isa
位图
arm64
x86_64
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
使用联合体 + 位域
的方式存储,优化内存空间- 类型分为
nonpointer
和非nonpointer
- 非
nonpointer
只存储指针地址,nonpointer
还存储类的其他信息
4. ISA_MASK
ISA_MASK
:宏定义,不同CPU
架构下的值不一样。nonpointer
类型isa
,需要isa
和ISA_MASK
进行&
运算,才能得到类对象地址
4.1 isa & ISA_MASK
在x86_64
架构下,ISA_MASK
定义为:
define ISA_MASK 0x00007ffffffffff8ULL
ULL
:表示unsigned long long
常量
打开main.m
文件,写入以下代码:
int main(int argc, const char * argv[]) {
@autoreleasepool {
LGPerson *per= [LGPerson alloc];
}
return 0;
}
获取isa
x/4g per
-------------------------
0x10077b200: 0x011d80010000832d 0x0000000000000000
0x10077b210: 0x0000000000000000 0x0000000000000000
isa
:0x011d80010000832d
通过和ISA_MASK
进行&
运算,得到类对象地址
p/x 0x011d800100008335 & 0x00007ffffffffff8ULL
-------------------------
0x0000000100008330
和per.class
打印结果一致
p/x per.class
-------------------------
0x0000000100008330 LGPerson
4.2 ISA_MASK
的本质
p/t 0x00007ffffffffff8ULL
-------------------------
0b0000000000000000011111111111111111111111111111111111111111111000
- 在
x86_64
架构下,ISA_MASK
的高17位
和低3位
为0
,中间44位
为1
。也就是说,只显示isa
中的shiftcls
部分。即:存储类指针的值
ISA_MASK
也称之为类的面具,和ISA_MASK
进行多次&
,得到的结果相同
p/x 0x0000000100008330 & 0x00007ffffffffff8ULL
-------------------------
0x0000000100008330
- 其原理,好比是相同的面具,无论戴多少层,所露出的位置都是一致的
5. isa的位运算
在x86_64
架构中,shiftcls
存储在3~47
位。即使我们不知道ISA_MASK
的存在,直接通过isa
的位运算,同样可以得到类对象地址
获取isa
x/4g per
-------------------------
0x10077b200: 0x011d80010000832d 0x0000000000000000
0x10077b210: 0x0000000000000000 0x0000000000000000
isa
:0x011d80010000832d
向右平移3位
p/x 0x011d800100008335 >> 3
-------------------------
0x0023b00020001066
向左平移20位
p/x 0x0023b00020001066 << 20
-------------------------
0x0002000106600000
向右平移17位
,得到类对象地址
p/x 0x0002000106600000 >> 17
-------------------------
0x0000000100008330
和per.class
打印结果一致
p/x per.class
-------------------------
0x0000000100008330 LGPerson
总结
窥探底层的方法
- 使用
clang
命令,将⽬标⽂件编译成C++
⽂件 使用
xcrun
命令,在clang
的基础上进⾏了⼀些封装,要更好⽤⼀些
对象本质
对象的本质是结构体
- 类也是对象,本质同样是结构体
- 万物皆对象,万物皆有
isa
isa
本质是结构体指针,占8字节
所有对象的底层实现,本质上都来自于
objc_object
结构体
位域
一种数据结构
可以使数据单元节省储存空间
联合体
成员之间内存共用,各变量是“互斥”的
- 可节省内存空间
- 联合体的大小,为最大成员变量的大小
- 一般会配合位域一起使用
缺点:包容性较差
isa
关联类信息
使用
联合体 + 位域
的方式存储,优化内存空间- 类型分为
nonpointer
和非nonpointer
非
nonpointer
只存储指针地址,nonpointer
还存储类的其他信息ISA_MASK
在
x86_64
架构下,ISA_MASK
的高17位
和低3位
为0
,中间44
位为1
。目的,显示isa
中的shiftcls
nonpointer
类型isa
,使用isa & ISA_MASK
,得到类对象地址isa
的位运算
向右平移
3位
- 向左平移
20位
- 向右平移
17位
- 得到类对象地址