对象本质
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 <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的实现为结构体- 结构体嵌套相当于伪继承
找到
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; }
获取
isax/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平移,同样可以得到类对象地址
获取
isax/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中的shiftclsnonpointer类型isa,使用isa & mask,得到类对象地址
isa的位运算
- 向右平移
3位- 向左平移
20位- 向右平移
17位- 得到类对象地址

