1. 对象本质

1.1 clang


clang是⼀个由Apple主导编写,基于LLVMC/C++/Objective-C的轻量级编译器。源代码发布于BSD协议下。将⽀持其普通lambda表达式、返回类型的简化处理以及更好的处理constexpr关键字

20134⽉,clang已经全⾯⽀持C++11标准,并开始实现C++1y特性(也就是C++14,这是C++的下⼀个⼩更新版本)。clang将⽀持其普通lambda表达式、返回类型的简化处理以及更好的处理constexpr关键字

clangGNU C语⾔规范⼏乎完全兼容(当然,也有部分不兼容的内容,包括编译命令选项也会有点差异),并在此基础上增加了额外的语法特性,⽐如C函数重载(通过__attribute__((overloadable))来修饰函数),其⽬标(之⼀)就是超越GCC

使用clang命令,将⽬标⽂件编译成C++⽂件

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


遇到UIKit错误

  1. main.m:9:9: fatal error: 'UIKit/UIKit.h' file not found


使用以下参数:

  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路径

1.2 xcrun

安装Xcode时,顺带安装了xcrun命令。xcrun命令在clang的基础上进⾏了⼀些封装,要更好⽤⼀些

生成模拟器版本

  1. xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp

生成真机版本

  1. xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp

1.3 探索对象本质

使用clang还原Objective-C代码在底层的实现

打开man.m函数,写入以下代码:

  1. #import <Foundation/Foundation.h>
  2. #import <objc/runtime.h>
  3. @interface LGPerson : NSObject
  4. @property (nonatomic, strong) NSString *Name;
  5. @end
  6. @implementation LGPerson
  7. @end
  8. int main(int argc, const char * argv[]) {
  9. @autoreleasepool {
  10. NSLog(@"Hello, World!");
  11. }
  12. return 0;
  13. }


使用clang命令,生成main.cpp文件

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

通过cpp文件,探索对象本质

找到LGPerson的定义与实现

  1. typedef struct objc_object LGPerson;
  2. struct LGPerson_IMPL {
  3. struct NSObject_IMPL NSObject_IVARS;
  4. NSString *_Name;
  5. };
  • LGPerson定义为objc_object类型
  • LGPerson_IMPLLGPerson的底层实现
  • LGPerson_IMPL中嵌套NSObject_IMPL结构体
  • 结构体嵌套相当于伪继承

找到NSObject的定义与实现

  1. typedef struct objc_object NSObject;
  2. struct NSObject_IMPL {
  3. Class isa;
  4. };
  • NSObject定义为objc_object类型
  • NSObject_IMPLNSObject的底层实现
  • 只有一个Class类型的成员变量isa

找到Class的定义与实现

  1. typedef struct objc_class *Class;
  2. struct objc_class {
  3. Class _Nonnull isa __attribute__((deprecated));
  4. } __attribute__((unavailable));
  • Class类型,本质是objc_class结构体指针,占8字节

找到objc_object的实现

  1. struct objc_object {
  2. Class _Nonnull isa __attribute__((deprecated));
  3. };
  • 只有一个Class类型的成员变量isa
  • 所有对象的底层实现,本质上都来自于objc_object结构体

结论:

  • 对象的本质是结构体
  • 类也是对象,本质同样是结构体
  • 万物皆对象,万物皆有isa
  • isa本质是结构体指针,占8字节
  • 所有对象的底层实现,本质上都来自于objc_object结构体

1.4 属性的getter/setter方法

  1. static NSString * _I_LGPerson_Name(LGPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_Name)); }
  2. static void _I_LGPerson_setName_(LGPerson * self, SEL _cmd, NSString *Name) { (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_Name)) = Name; }
  • 包含self_cmd隐藏参数
  1. (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_Name))
  • 通过内存平移进行读写操作
  • self为结构体首地址
  • OBJC_IVAR_$_LGPerson$_Name为属性的偏移地址

1.5 id类型

  1. typedef struct objc_object *id;
  • id为别名,本质是objc_object结构体指针
  • 所以,在OC中,定义id类型不用加*
  • 由于所有对象的底层实现,本质上都来自于objc_object结构体,故此id类型可以存储任意对象

2. 联合体 / 位域

2.1 位域

信息的存取一般以字节为单位。实际上,有时存储一个信息不必用一个或多个字节,例如:“真/假”值,用01表示,只需1位即可

位域(Bit field):为一种数据结构,可以把数据以位的形式紧凑的储存,并允许程序员对此结构的位进行操作

使用位域的优势:

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

案例:

  1. struct LGCar1 {
  2. BOOL front;
  3. BOOL back;
  4. BOOL left;
  5. BOOL right;
  6. };
  • LGCar1结构体中,包含前、后、左、右四个BOOL类型的成员变量
  1. struct LGCar1 car1;
  2. NSLog(@"LGCar1:%ld",sizeof(car1));
  3. -------------------------
  4. LGCar14
  • 4字节,即32位

4个“真/假”值,分配4字节,实属空间浪费。正常来说,使用4位就可表示,占1字节即可

  1. 0000 1111
  • 使用低4位,分别表示前、后、左、右

使用位域节省储存空间

  1. struct LGCar2 {
  2. BOOL front : 1;
  3. BOOL back : 1;
  4. BOOL left : 1;
  5. BOOL right : 1;
  6. };
  • 语法:在成员变量后面增加: x,表示其所占位数
  1. struct LGCar2 car2;
  2. NSLog(@"LGCar2:%ld",sizeof(car2));
  3. -------------------------
  4. LGCar21
  • 仅占1字节4位就够用

2.2 联合体

结构体(struct):所有变量是“共存”的

  • 优点:包容性强,成员之间不会相互影响
  • 缺点:内存空间的浪费,不管⽤不⽤,全分配
  1. struct LGTeacher1 {
  2. char *name;
  3. int age;
  4. };
  • 定义结构体,包含nameage两个成员变量
  1. struct LGTeacher1 teacher1;
  2. teacher1.name = "Cooci";
  3. teacher1.age = 18;
  4. NSLog(@"name:%s,age:%i,sizeof:%ld", teacher1.name, teacher1.age, sizeof(teacher1));
  5. -------------------------
  6. nameCoociage18sizeof16
  • 成员之间不会相互影响,占16字节

联合体(union):成员之间内存共用,各变量是“互斥”的

  • 优点:节省内存空间,内存的使⽤更为精细灵活
  • 缺点:包容性较差
  1. union LGTeacher2 {
  2. char *name;
  3. int age;
  4. };
  • 定义联合体,包含nameage两个成员变量
  1. struct LGTeacher1 teacher1;
  2. teacher1.age = 18;
  3. teacher1.name = "Cooci";
  4. NSLog(@"name:%s,age:%i,sizeof:%ld", teacher1.name, teacher1.age, sizeof(teacher1));
  5. -------------------------
  6. nameCoociage15934sizeof8
  • 成员之间内存共用,只有一个成员变量有值。由于name赋值,导致age变为坏地址
  • 联合体的大小,为最大成员变量的大小
  • 联合体一般会配合位域一起使用

2.3 项目中的使用


打开LGCar.m文件,写入以下代码:

宏定义

  1. #import "LGCar.h"
  2. #define LGDirectionFrontMask (1 << 0)
  3. #define LGDirectionBackMask (1 << 1)
  4. #define LGDirectionLeftMask (1 << 2)
  5. #define LGDirectionRightMask (1 << 3)

定义联合体 + 位域

  1. @interface LGCar() {
  2. union {
  3. char bits;
  4. struct {
  5. char front : 1;
  6. char back : 1;
  7. char left : 1;
  8. char right : 1;
  9. };
  10. } _direction;
  11. }
  12. @end

初始化

  1. - (instancetype)init {
  2. self = [super init];
  3. if (self) {
  4. _direction.bits = 0b0000000000;
  5. }
  6. return self;
  7. }

frontgetter/setter方法

  1. - (void)setFront:(BOOL)isFront {
  2. if (isFront) {
  3. _direction.bits |= LGDirectionFrontMask;
  4. } else {
  5. _direction.bits |= ~LGDirectionFrontMask;
  6. }
  7. NSLog(@"%s",__func__);
  8. }
  9. - (BOOL)isFront{
  10. return _direction.front;
  11. }

3. isa关联类信息

initInstanceIsa函数是alloc的核心方法之一,负责将classisa进行关联

3.1 探索objc源码

3.1.1 initInstanceIsa函数

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

3.1.2 initIsa函数

  1. inline void
  2. objc_object::initIsa(Class cls, bool nonpointer, >UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT bool hasCxxDtor) {
  3. ASSERT(!isTaggedPointer());
  4. isa_t newisa(0);
  5. if (!nonpointer) {
  6. newisa.setClass(cls, this);
  7. } else {
  8. ASSERT(!DisableNonpointerIsa);
  9. ASSERT(!cls->instancesRequireRawIsa());
  10. #if SUPPORT_INDEXED_ISA
  11. ASSERT(cls->classArrayIndex() > 0);
  12. newisa.bits = ISA_INDEX_MAGIC_VALUE;
  13. newisa.has_cxx_dtor = hasCxxDtor;
  14. newisa.indexcls = (uintptr_t)cls->classArrayIndex();
  15. #else
  16. newisa.bits = ISA_MAGIC_VALUE;
  17. # if ISA_HAS_CXX_DTOR_BIT
  18. newisa.has_cxx_dtor = hasCxxDtor;
  19. # endif
  20. newisa.setClass(cls, this);
  21. #endif
  22. newisa.extra_rc = 1;
  23. }
  24. isa = newisa;
  25. }
  • 初始化isa_t
  • nonpointer,直接设置class
  • 否则,对bits赋值,关联类信息

找到SUPPORT_INDEXED_ISA的定义:

  1. #if __ARM_ARCH_7K__ >= 2 || (__arm64__ && !__LP64__)
  2. # define SUPPORT_INDEXED_ISA 1
  3. #else
  4. # define SUPPORT_INDEXED_ISA 0
  5. #endif
  • 条件判断,默认为# define SUPPORT_INDEXED_ISA 1的分支

3.1.3 isa_t联合体

  1. union isa_t {
  2. isa_t() { }
  3. isa_t(uintptr_t value) : bits(value) { }
  4. uintptr_t bits;
  5. private:
  6. Class cls;
  7. public:
  8. #if defined(ISA_BITFIELD)
  9. struct {
  10. ISA_BITFIELD;
  11. };
  12. bool isDeallocating() {
  13. return extra_rc == 0 && has_sidetable_rc == 0;
  14. }
  15. void setDeallocating() {
  16. extra_rc = 0;
  17. has_sidetable_rc = 0;
  18. }
  19. #endif
  20. void setClass(Class cls, objc_object *obj);
  21. Class getClass(bool authenticated);
  22. Class getDecodedClass(bool authenticated);
  23. };
  • isa_t为联合体
  • bitscls两个成员变量,它们是互斥的
  • ISA_BITFIELDisa结构

3.1.4 isa结构

  1. # if __arm64__
  2. # if __has_feature(ptrauth_calls) || TARGET_OS_SIMULATOR
  3. # define ISA_MASK 0x007ffffffffffff8ULL
  4. # define ISA_MAGIC_MASK 0x0000000000000001ULL
  5. # define ISA_MAGIC_VALUE 0x0000000000000001ULL
  6. # define ISA_HAS_CXX_DTOR_BIT 0
  7. # define ISA_BITFIELD \
  8. uintptr_t nonpointer : 1; \
  9. uintptr_t has_assoc : 1; \
  10. uintptr_t weakly_referenced : 1; \
  11. uintptr_t shiftcls_and_sig : 52; \
  12. uintptr_t has_sidetable_rc : 1; \
  13. uintptr_t extra_rc : 8
  14. # define RC_ONE (1ULL<<56)
  15. # define RC_HALF (1ULL<<7)
  16. # else
  17. # define ISA_MASK 0x0000000ffffffff8ULL
  18. # define ISA_MAGIC_MASK 0x000003f000000001ULL
  19. # define ISA_MAGIC_VALUE 0x000001a000000001ULL
  20. # define ISA_HAS_CXX_DTOR_BIT 1
  21. # define ISA_BITFIELD \
  22. uintptr_t nonpointer : 1; \
  23. uintptr_t has_assoc : 1; \
  24. uintptr_t has_cxx_dtor : 1; \
  25. uintptr_t shiftcls : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
  26. uintptr_t magic : 6; \
  27. uintptr_t weakly_referenced : 1; \
  28. uintptr_t unused : 1; \
  29. uintptr_t has_sidetable_rc : 1; \
  30. uintptr_t extra_rc : 19
  31. # define RC_ONE (1ULL<<45)
  32. # define RC_HALF (1ULL<<18)
  33. # endif
  34. # elif __x86_64__
  35. # define ISA_MASK 0x00007ffffffffff8ULL
  36. # define ISA_MAGIC_MASK 0x001f800000000001ULL
  37. # define ISA_MAGIC_VALUE 0x001d800000000001ULL
  38. # define ISA_HAS_CXX_DTOR_BIT 1
  39. # define ISA_BITFIELD \
  40. uintptr_t nonpointer : 1; \
  41. uintptr_t has_assoc : 1; \
  42. uintptr_t has_cxx_dtor : 1; \
  43. uintptr_t shiftcls : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \
  44. uintptr_t magic : 6; \
  45. uintptr_t weakly_referenced : 1; \
  46. uintptr_t unused : 1; \
  47. uintptr_t has_sidetable_rc : 1; \
  48. uintptr_t extra_rc : 8
  49. # define RC_ONE (1ULL<<56)
  50. # define RC_HALF (1ULL<<7)
  51. # else
  52. # error unknown architecture for packed isa
  53. # endif
  • 不同CPU架构,isa结构略有差异
  • __has_feature:编译器是否支持该功能
  • ptrauth_callsarm64e架构,引入指针验证代码 (PAC)

◦ 用于检测和防止内存中指针的意外更改
◦ 支持Apple A12或更高版本A系列处理器的设备(如:XS/XS Max/XR
◦ 详情可参见:官方文档

  • TARGET_OS_SIMULATOR:判断是否为模拟器

3.2 isa位图

arm64
image.png

x86_64
image.png


  • 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_rc9。如果引⽤计数⼤于 10,则需要使⽤到下⾯的has_sidetable_rc

结论:

  • isa使用联合体 + 位域的方式存储,优化内存空间
  • 类型分为nonpointer和非nonpointer
  • nonpointer只存储指针地址,nonpointer还存储类的其他信息

4. ISA_MASK

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

4.1 isa & ISA_MASK

x86_64架构下,ISA_MASK定义为:

  1. define ISA_MASK 0x00007ffffffffff8ULL
  • ULL:表示unsigned long long常量

打开main.m文件,写入以下代码:

  1. int main(int argc, const char * argv[]) {
  2. @autoreleasepool {
  3. LGPerson *per= [LGPerson alloc];
  4. }
  5. return 0;
  6. }

获取isa

  1. x/4g per
  2. -------------------------
  3. 0x10077b200: 0x011d80010000832d 0x0000000000000000
  4. 0x10077b210: 0x0000000000000000 0x0000000000000000
  • isa0x011d80010000832d

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

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

per.class打印结果一致

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

4.2 ISA_MASK的本质

  1. p/t 0x00007ffffffffff8ULL
  2. -------------------------
  3. 0b0000000000000000011111111111111111111111111111111111111111111000
  • x86_64架构下,ISA_MASK高17位低3位0,中间44位1。也就是说,只显示isa中的shiftcls部分。即:存储类指针的值

ISA_MASK也称之为类的面具,和ISA_MASK进行多次&,得到的结果相同

  1. p/x 0x0000000100008330 & 0x00007ffffffffff8ULL
  2. -------------------------
  3. 0x0000000100008330
  • 其原理,好比是相同的面具,无论戴多少层,所露出的位置都是一致的

5. isa的位运算

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

获取isa

  1. x/4g per
  2. -------------------------
  3. 0x10077b200: 0x011d80010000832d 0x0000000000000000
  4. 0x10077b210: 0x0000000000000000 0x0000000000000000
  • isa0x011d80010000832d

向右平移3位

  1. p/x 0x011d800100008335 >> 3
  2. -------------------------
  3. 0x0023b00020001066

向左平移20位

  1. p/x 0x0023b00020001066 << 20
  2. -------------------------
  3. 0x0002000106600000

向右平移17位,得到类对象地址

  1. p/x 0x0002000106600000 >> 17
  2. -------------------------
  3. 0x0000000100008330

per.class打印结果一致

  1. p/x per.class
  2. -------------------------
  3. 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位
  • 得到类对象地址