对象本质
clang
clang
是⼀个C语⾔
、C++
、Objective-C
语⾔的轻量级编译器。源代码发布于BSD
协议下
clang
将⽀持其普通lambda
表达式、返回类型的简化处理以及更好的处理constexpr
关键字
clang
是⼀个由Apple
主导编写,基于LLVM
的C/C++/Objective-C
编译器
2013
年4
⽉,clang
已经全⾯⽀持C++11
标准,并开始实现C++1y
特性(也就是C++14
,这是C++
的下⼀个⼩更新版本)。clang
将⽀持其普通lambda
表达式、返回类型的简化处理以及更好的处理constexpr
关键字
clang
是⼀个C++
编写、基于LLVM
、发布于LLVM BSD
许可证下的C/C++/Objective-C/Objective-C++
编译器。它与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
路径
安装
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
使用
clang
还原Objective-C
代码在底层的实现打开
man.m
函数,写入以下代码:#import
#import @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
的实现为结构体- 结构体嵌套相当于伪继承
找到
NSObject_IMPL
的实现struct NSObject_IMPL { Class isa; };
NSObject
的底层实现- 包含成员变量
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)); };
- 对象的本质是结构体
- 类也是对象,本质同样是结构体
OC
中对象继承于NSObject
,底层是objc_object
结构体isa
是结构体指针,占8字节
属性的
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
为属性的偏移地址
id
类型typedef struct objc_object *id;
- 和
Class
同理,底层为结构体指针类型- 所以在
OC
中,定义id
类型不用加*
位域
信息的存取一般以字节为单位。实际上,有时存储一个信息不必用一个或多个字节,例如:“真/假”值,用
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字节
属于空间浪费。四个BOOL
类型,使用4位
就可以表示,占1字节
即可0000 1111
- 从右往左,前四位,分别表示前、后、左、右
使用位域优化内存
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位
就够用
联合体
结构体(
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
为Bad Address
- 联合体的大小,为最大成员变量的大小
- 联合体一般会配合位域一起使用
联合体 + 位域
在项目中的使用打开
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; }
isa关联类信息
initInstanceIsa
函数是alloc
的核心方法之一,负责将class
与isa
进行关联打开
objc4-818.2
源码进入
initInstanceIsa
函数inline void objc_object::initInstanceIsa(Class cls, bool hasCxxDtor) { ASSERT(!cls->instancesRequireRawIsa()); ASSERT(hasCxxDtor == cls->hasCxxDtor()); initIsa(cls, true, hasCxxDtor); }
进入
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
- 非
NonpointerIsa
,直接设置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
的分支
找到
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_BITFIELD
的定义:# 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
arm64
x86_64
isa
使用联合体 + 位域
的方式存储,优化内存空间- 类型分为
nonpointer
和非nonpointer
- 非
nonpointer
只存储指针地址,nonpointer
还存储类的其他信息
ISA_MASK
ISA_MASK
:宏定义,不同CPU
架构下的值不一样。nonpointer
类型isa
,需要isa
和ISA_MASK
进行&
(与运算),才能得到类对象地址
案例:
打开
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
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
- 其原理,好比是相同的面具,无论戴多少层,所露出的位置都是一致的
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
的基础上进⾏了⼀些封装,要更好⽤⼀些对象本质
- 对象的本质是结构体
- 类也是对象,本质同样是结构体
OC
中对象继承于NSObject
,底层是objc_object
结构体isa
是结构体指针,占8字节位域
- 一种数据结构
- 可以使数据单元节省储存空间
联合体
- 成员之间内存共用,各变量是“互斥”的
- 可节省内存空间
- 联合体的大小,为最大成员变量的大小
- 一般会配合位域一起使用
- 缺点:包容性较差
isa
关联类信息
- 使用
联合体 + 位域
的方式存储,优化内存空间- 类型分为
nonpointer
和非nonpointer
- 非
nonpointer
只存储指针地址,nonpointer
还存储类的其他信息
ISA_MASK
- 在
x86_64
架构下,ISA_MASK
的高17位
和低3位
为0
,中间44
位为1
。目的,显示isa
中的shiftcls
nonpointer
类型isa
,使用isa & mask
,得到类对象地址
isa
的位运算
- 向右平移
3位
- 向左平移
20位
- 向右平移
17位
- 得到类对象地址