一、对象的本质
1.1 clang
1.1.1clang 概述
Clang
是一个C语言
、C++
、Objective-C
语言的轻量级编译器。源代码发布于BSD
协议下。 Clang
将支持其普通lambda
表达式、返回类型的简化处理以及更好的处理constexpr
关键字。
Clang
是一个由Apple
主导编写,基于LLVM
的C/C++/Objective-C
编译器。
它与GNU C
语言规范几乎完全兼容(当然,也有部分不兼容的内容, 包括编译命令选项也会有点差异),并在此基础上增加了额外的语法特性,比如C
函数重载 (通过__attribute__((overloadable))
来修饰函数),其目标(之一)就是超越GCC
。
1.1.2 clang与xcrun命令
1.1.2.1 clang
把目标文件编译成c++
文件,最简单的方式:
clang -rewrite-objc main.m -o main.cpp
如果包含其它SDK
,比如UIKit
则需要指定isysroot
:
clang -rewrite-objc -fobjc-arc -fobjc-runtime=ios-14.0.0 -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk main.m -o main.cpp
更多clang命令参考
- 如果找不到
Foundation
则需要排查clang
版本设置是否正确。使用which clang
可以直接查看路径。有些公司会使用clang-format
来进行代码格式化,需要排查环境变量中是否导出了相关路径(如果导出先屏蔽掉)。正常路径为/usr/bin/clang
。isysroot
也可以导出环境变量进行配置方便使用。
1.1.2.2 xcrun(推荐)
xcode
安装的时候顺带安装了xcrun
命令,xcrun
命令在clang
的基础上进行了 一些封装,比clang
更好用。
模拟器命令:
xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc main.m -o main-arm64simulator.cpp
真机命令:
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp
1.2 对象c++代码分析
main.m
文件如下,直接生成对应的.cpp
文件对HotpotCat
进行分析。
#import
@interface HotpotCat : NSObject
@end
@implementation HotpotCat
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
}
return 0;
}
由于clang
生成的文件过大,推荐使用xcrun
生成.cpp
文件进行分析。
上面的代码使用
clang
最简单的方式生成的大小为4.4MB
11万行
,而用xcrun
生成的代码大小1.7MB
3.4万行
。
1.2.1 对象在底层是结构体
直接搜索HotpotCat
有如下代码:
可以看到生成了HotpotCat_IMPL
是一个结构体,那么HotpotCat_IMPL
就是HotpotCat
的底层实现么?对HotpotCat
增加属性hp_name
:
@property(nonatomic, copy) NSString *hp_name;
重新生成.cpp
文件:
这也就验证了HotpotCat_IMPL
就是HotpotCat
的底层实现,那么说明: 对象在底层的本质就是结构体。
在HotpotCat_IMPL
结构体中又嵌套了NSObject_IMPL
结构体,这可以理解为继承。
NSObject_IMPL
定义如下:
struct NSObject_IMPL {
Class isa;
};
所以NSObject_IVARS
就是成员变量isa
。
1.2.2 objc_object & objc_class
在HotpotCat_IMPL
上面有如下代码:
typedef struct objc_object HotpotCat;
为什么HotpotCat
是objc_object
类型?这是因为NSObject
的底层实现就是objc_object
。
同样的Class
定义如下:
typedef struct objc_class *Class;
是objc_class
类型的结构体指针。
同样可以看到id
为objc_object
结构体类型指针。
typedef struct objc_object *id;
这也就是id
声明的时候不需要*
的原因。
1.2.3 setter & getter
在.cpp
文件中有以下代码:
// @property(nonatomic, copy) NSString *hp_name;
/* @end */
// @implementation HotpotCat
//这里是setter和getter 参数self _cmd 隐藏参数
static NSString * _I_HotpotCat_hp_name(HotpotCat * self, SEL _cmd) {
//return self + 成员变量偏移
return (*(NSString **)((char *)self + OBJC_IVAR_$_HotpotCat$_hp_name));
}
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);
static void _I_HotpotCat_setHp_name_(HotpotCat * self, SEL _cmd, NSString *hp_name) {
objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct HotpotCat, _hp_name), (id)hp_name, 0, 1);
}
// @end
- 根据系统默认注释和函数名称确认这里是
hp_name
的setter
和getter
方法。 -
getter
方法返回hp_name
是通过self + 成员变量偏移
获取的。getter
同理。
二、位域
struct Direction {
BOOL left;
BOOL right;
BOOL front;
BOOL back;
};
上面是一个记录方向的结构体。这个结构体占用4
字节32
位:00000000 00000000 00000000 00000000
。但是对于BOOL
值只有两种情况YES
/NO
。那么如果能用4
位0000
来代替前后左右,就只需要0.5
个字节就能表示这个数据结构了(虽然只需要0.5
字节,但是数据单元最小为1
字节)。那么Direction
的实现显然浪费了3
倍的空间。有什么优化方式呢?位域
2.1 结构体位域
修改Direction
为HPDirection
:
struct HPDirection {
BOOL left : 1;
BOOL right : 1;
BOOL front : 1;
BOOL back : 1;
};
格式为:数据类型 位域名称:位域长度。
验证:
struct Direction dir;
dir.left = YES;
dir.right = YES;
dir.front = YES;
dir.back = YES;
struct HPDirection hpDir;
hpDir.left = YES;
hpDir.right = YES;
hpDir.front = YES;
hpDir.back = YES;
printf("\nDirection size:%zu\nHPDirection size:%zu\n",sizeof(dir),sizeof(hpDir));
-
dir
分别存储在4
个字节中,并且只占了1
位。 -
hpDir
存储在1
个字节中,占用了4
位。 -
dir
占用4
字节内存,hpDir
占用1
字节。
2.2 联合体
2.2.1结构体&联合体对比
//结构体联合体对比
//共存
struct HPStruct {
char *name;
int age;
double height;
};
//互斥
union HPUnion {
char *name;
int age;
double height;
};
void testStructAndUnion() {
struct HPStruct s;
union HPUnion u;
s.name = "HotpotCat";
u.name = "HotpotCat";
s.age = 18;
u.age = 18;
s.height = 180.0;
u.height = 180.0;
}
分别定义了HPStruct
结构体和HPUnion
共用体,在整个赋值过程中变化如下:
总结:
- 结构体(
struct
)中所有变量是“共存”的。
优点:“有容乃大”, 全面;
缺点:struct
内存空间的分配是粗放的,不管用不用全分配。 - 联合体/共用体(
union
)中是各变量是“互斥”的。
缺点:不够“包容”;
优点:内存使用更为精细灵活,节省了内存空间。 - 联合体在未进行赋值前数据成员会存在脏数据。
2.2.2 联合体位域
HPDirectionItem.h
:
@interface HPDirectionItem : NSObject
@property (nonatomic, assign) BOOL left;
@property (nonatomic, assign) BOOL right;
@property (nonatomic, assign) BOOL front;
@property (nonatomic, assign) BOOL back;
@end
HPDirectionItem.m
:
#define HPDirectionLeftMask (1 << 0)
#define HPDirectionRightMask (1 << 1)
#define HPDirectionFrontMask (1 << 2)
#define HPDirectionBackMask (1 << 3)
#import "HPDirectionItem.h"
@interface HPDirectionItem () {
//这里bits和struct用任一一个就可以,结构体相当于是对bits的解释。因为是共用体用同一块内存。
union {
char bits;
//位域,这里是匿名结构体(anonymous struct)
struct {
char left : 1;
char right : 1;
char front : 1;
char back : 1;
};
}_direction;
}
@end
@implementation HPDirectionItem
- (instancetype)init {
self = [super init];
if (self) {
_direction.bits = 0b00000000;
}
return self;
}
- (void)setLeft:(BOOL)left {
if (left) {
_direction.bits |= HPDirectionLeftMask;
} else {
_direction.bits &= ~HPDirectionLeftMask;
}
}
- (BOOL)left {
return _direction.bits & HPDirectionLeftMask;
}
//……
//其它方向设置同理
//……
@end
-
HPDirectionItem
是一个方向类,类中有一个_direction
的共用体。 -
_direction
中有bits
和anonymous struct
,这里anonymous struct
相当于是对bits
的一个解释(因为是共用体,同一个字节内存。下面的调试截图很好的证明力这一点)。 - 通过对
bits
位移操作来进行数据的存储,其实就相当于对结构体位域的操作。连这个可以互相操作。
所以可以将setter
和getter
通过结构体去操作,效果和操作bits
相同:
- (void)setLeft:(BOOL)left {
_direction.left = left;
}
- (BOOL)left {
return _direction.left;
}
当然也可以两者混用:
- (void)setLeft:(BOOL)left {
_direction.left = left;
}
- (BOOL)left {
return _direction.bits & HPDirectionLeftMask;
}
根本上还是对同一块内存空间进行操作。
调用:
void testUnionBits() {
HPDirectionItem *item = [HPDirectionItem alloc];
item.left = 1;
item.right = 1;
item.front = 1;
item.back = 1;
item.right = 0;
item.back = 0;
NSLog(@"testUnionBits");
}
这样整个赋值流程就符合预期满足需求了。
- 联合体位域作用:优化内存空间和访问速度。
详细内容见:BitsTest Demo
三、 isa
在alloc
分析的文章中已经了解到执行完initIsa
后将alloc
开辟的内存与类进行了关联。在initIsa
中首先创建了isa_t
也就是isa
,去掉方法后它的主要结构如下:
union isa_t {
//……
uintptr_t bits;
private:
Class cls;
public:
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
};
//……
#endif
//……
};
它是一个union
,包含了bits
、cls
(私有)和一个匿名结构体,所以这3
个其实是一个内容,不同表现形式罢了。这个结构似曾相识,与2.2.2
中联合体位域一样。不同的是isa_t
占用8
字节64
位。
没有关联类时isa
分布(默认都是0
,没有指向):
关联isa
后:
bits
和cls
分析起来比较困难,既然三者一样,那么isa_t
的核心就是ISA_BITFIELD
。
3.1 isa结构分析
ISA_BITFIELD
宏定义展开结构图下。
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)
arm64
:
# 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)
根据上面的定义isa
位域分布如下图:
-
nonpointer
:表示是否对isa
指针开启指针优化。0
表示纯isa
指针,1
表示不止是类对象地址,isa
中包含了类信息、对象的引用计数等。也就是使用位域保存更多信息。 -
has_assoc
:关联对象标志位,0
不存在1
存在。 -
has_cxx_dtor
:该对象是否有C++
/Objc
的析构器,如果有析构函数,则需要做析构逻辑; 如果没有,则可以更快的释放对象。 -
shiftcls
:存储类指针的值。开启指针优化的情况下,在arm64
架构中有33
(3~35
) 位用来存储类指针。存放着Class
、Meta-Class
对象的内存地址。 -
magic
:用于调试器判断当前对象是真的对象还是没有初始化的空间。 -
weakly_referenced
:志对象是否被指向或者曾经指向一个ARC
的弱变量,没有弱引用的对象可以更快释放。 -
deallocating
:标志对象是否正在释放内存。 -
has_sidetable_rc
:散列表。是否需要使用sidetable
来存储引用计数。少量的引用计数是不会直接存放在SideTables
表中,与extra_rc
有关。 -
extra_rc
:表示该对象的引用计数值,实际上是引用计数值减1
。 当extra_rc
被存满时引用计数会存入SideTables
中,SideTables
中有很多张SideTable
,每个SideTable
也都是一个散列表,而引用计数表就包含在SideTable
之中。在这里x86
架构下最大为28,arm64
下为219。例如:extra_rc:3
如果对象的引用计数为8
,那么extra_rc
为7
。如果引用计数大于8
, 则需要使用到has_sidetable_rc
。
isa
就是一个类的指针,但是指针用不了64
位。并且每一个类都有isa
。所以通过位域来存储优化内存和访问速度。将与类相关的一些内容存入了isa
中。这个时候isa
就不是一个简单的地址了。
3.1.1 nonpointer配置
那么什么情况下nonpointer
会为0
,isa
为一个纯指针呢?在_class_createInstanceFromZone
方法中有如下代码:
bool fast = cls->canAllocNonpointer();
if (!zone && fast) {
obj->initInstanceIsa(cls, hasCxxDtor);
} else {
obj->initIsa(cls);
}
可以看到核心逻辑是canAllocNonpointer
:
#define FAST_CACHE_REQUIRES_RAW_ISA (1<<13)
bool canAllocNonpointer() {
ASSERT(!isFuture());
return !instancesRequireRawIsa();
}
bool instancesRequireRawIsa() {
return cache.getBit(FAST_CACHE_REQUIRES_RAW_ISA);
}
可以看到是取的缓存的标志位,并没有特别有用的信息。那么就在cache.setBit
打个条件断点:
最终找到了调用的地方是realizeClassWithoutSwift
方法:
既然
nonpointer
为1
的时候走的是if
分支,那么为0
肯定就走else
分支了。这个时候找到了DisableNonpointerIsa
,点进去可以看到定义如下:
看到
objc-env
就想到了环境变量。那么配置OBJC_DISABLE_NONPOINTER_ISA
为YES
(配置1
无效)。
配置nonpointer
环境变量:
验证nonpointer
:
这个时候isa
就是一个纯指针了。这里也可以看出纯指针浪费了很多空间,根本用不到64
位。
- 通过配置
OBJC_DISABLE_NONPOINTER_ISA
为YES
可以设置nonpointer
为0
。 - 更多环境变量配置可以在
objc
源码objc-env.h
文件中查看。
3.2 通过isa获取cls
3.2.1 通过mask验证
既然isa
与类关联后就有了类的信息,那么是怎么关联的呢?
首先要清楚MASK
的概念,MASK
翻译过来就是面具,其实它的作用也就是面具。比如戴面具要漏出眼睛,戴同样的面具漏出的部位是一样的,戴多层面具漏出的部位也是一样的。MASK
的作用就相当于去除不必要的位域数据,取到需要的位域数据。
-
isa & isa_mask -> cls
:cls
是通过 掩码mask
与isa
关联的。 -
cls & isa_mask -> cls
:多次mask
值不变。
0x00007ffffffffff8
就是二进制0b0000000000000000011111111111111111111111111111111111111111111000
。&
操作就相当于保留中间44
位,其余清零。
3.2.2 通过位域结构验证
上面已经清楚了isa
的位域分布结构。cls
就是shiftcls
。对于x86_64
来说shiftcls
占用44
位,前后分别3
(nonpointer + has_assoc + has_cxx_dtor
)位和17
(magic + weakly_referenced + unused + has_sidetable_rc + extra_rc
)位。那么我们可以通过位移获取到shiftcls
。
位移的过程中就相当于是擦除无用信息,只保留shiftcls
。变化过程如下:
-
isa >> 3
清空nonpointer + has_assoc + has_cxx_dtor
。 -
isa << 20
清空magic + weakly_referenced + unused + has_sidetable_rc + extra_rc
以及右移的3
位。 -
isa >> 17
位归位得到cls
值。
3.2.2 通过isa_t验证
除了上面通过位移获取cls
,还有一种更简单的方式。既然isa
在底层是isa_t
结构那么可以直接转成结构体获取shiftcls
:
-
isa
强转isa_t
获取shiftcls
值。 -
shiftcls
末尾补000
得到cls
地址。
3.3 代码验证isa与cls
既然类的本质是结构体,那么模仿结构体就能和对象互相转换了。模拟isa
和class
的代码如下:
#define ISA_MASK 0x00007ffffffffff8ULL
//模拟isa
union isa_t {
uintptr_t bits;
struct {
uintptr_t nonpointer : 1;
uintptr_t has_assoc : 1;
uintptr_t has_cxx_dtor : 1;
uintptr_t shiftcls : 44;
uintptr_t magic : 6;
uintptr_t weakly_referenced : 1;
uintptr_t unused : 1;
uintptr_t has_sidetable_rc : 1;
uintptr_t extra_rc : 8;
};
};
//模拟class
struct HPClass {
union isa_t isa;
};
转换测试:
NSObject *obj = [NSObject alloc];
struct HPClass *p = (__bridge void *)obj;
NSLog(@"nonponinter ISA:0x%lx",p->isa.bits);
NSLog(@"nonponinter:0x%x",p->isa.nonpointer);
NSLog(@"has_assoc:0x%x",p->isa.has_assoc);
NSLog(@"has_cxx_dtor:0x%x",p->isa.has_cxx_dtor);
NSLog(@"shiftcls:0x%lx",p->isa.shiftcls);
NSLog(@"cls(p->isa.bits):0x%llx == NSObject.class:0x%llx",p->isa.bits & ISA_MASK,NSObject.class);
NSLog(@"magic:0x%x",p->isa.magic);
NSLog(@"weakly_referenced:0x%x",p->isa.weakly_referenced);
NSLog(@"unused:0x%x",p->isa.unused);
NSLog(@"has_sidetable_rc:0x%x",p->isa.has_sidetable_rc);
NSLog(@"extra_rc:0x%x",p->isa.extra_rc);
结果:
nonponinter ISA:0x1dffff96af9119
nonponinter:0x1
has_assoc:0x0
has_cxx_dtor:0x0
shiftcls:0xffff2d5f223
cls(p->isa.bits):0x7fff96af9118 == NSObject.class:0x7fff96af9118
magic:0x3b
weakly_referenced:0x0
unused:0x0
has_sidetable_rc:0x0
extra_rc:0x0
Hello, World!
在这里对象完全转换成了结构体,再次说明对象本质就是结构体。p->isa.bits
与NSObject.class
相同都指向cls
。所以通过系统API
获取到的isa
就是cls
(class
方法底层对bits
进行了& MASK
操作,与这里的模拟代码相同)。
四、 initInstanceIsa源码分析
4.1 initIsa
isa
和类绑定最终是调用的initIsa
方法,核心源码如下:
inline void
objc_object::initIsa(Class cls, bool nonpointer, UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT bool hasCxxDtor)
{
isa_t newisa(0);
if (!nonpointer) {//纯指针
newisa.setClass(cls, this);
} else {//nonpointer指针
#if SUPPORT_INDEXED_ISA //表示在 armv7k or arm64_32架构下
newisa.bits = ISA_INDEX_MAGIC_VALUE;
newisa.has_cxx_dtor = hasCxxDtor;
newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
//64位
newisa.bits = ISA_MAGIC_VALUE;
newisa.has_cxx_dtor = hasCxxDtor;
newisa.setClass(cls, this);
#endif
newisa.extra_rc = 1;
}
isa = newisa;
}
从源码中可以看到纯指针的情况下直接调用setClass
进行赋值了,32
位架构的情况下也是能开启nonpointer
的,cls->classArrayIndex
赋值给了indexcls
。64
位的情况下也调用了setClass
。既然nonpointer
无论在什么情况下都调用了setClass
(这里除了32
位),那么就说明区分逻辑在setClass
方法中。
- 设置
bits
就相当于设置默认值(x86_64
下值为0x001d800000000001
),由于是共用体所以要放在最前面设置。
对应数据如下:
image.png
相当于nonpointer
设置为1
,magic
为0b111011
说明已经初始化了。magic
的具体含义先不在这里分析。
-
has_cxx_dtor
是传递进来的通过cls->hasCxxDtor()
获取的。 -
indexcls
相当于关联cls
(32
位)。 -
setClass
相当于关联cls
(64
位)。 -
extra_rc
引用计数初始化为1
。
那么核心的关联逻辑就是setClass
与cls->classArrayIndex
了。
4.2 setClass
setClass
源码如下如下:
inline void
isa_t::setClass(Class newCls, UNUSED_WITHOUT_PTRAUTH objc_object *obj)
{
#if __has_feature(ptrauth_calls) || TARGET_OS_SIMULATOR //arm64e/模拟器
//根据isa mode 进行标记操作
# if ISA_SIGNING_SIGN_MODE == ISA_SIGNING_SIGN_NONE
//nonpointer 为 0 直接赋值
uintptr_t signedCls = (uintptr_t)newCls;
//仅仅针对swift关联
# elif ISA_SIGNING_SIGN_MODE == ISA_SIGNING_SIGN_ONLY_SWIFT
//直接赋值
uintptr_t signedCls = (uintptr_t)newCls;
if (newCls->isSwiftStable())
//isSwiftStable表示该类是否是稳定的 Swift ABI 的 Swift 类
signedCls = (uintptr_t)ptrauth_sign_unauthenticated((void *)newCls, ISA_SIGNING_KEY, ptrauth_blend_discriminator(obj, ISA_SIGNING_DISCRIMINATOR));
# elif ISA_SIGNING_SIGN_MODE == ISA_SIGNING_SIGN_ALL
//这里与newCls->isSwiftStable()逻辑相同了
uintptr_t signedCls = (uintptr_t)ptrauth_sign_unauthenticated((void *)newCls, ISA_SIGNING_KEY, ptrauth_blend_discriminator(obj, ISA_SIGNING_DISCRIMINATOR));
# else
# error Unknown isa signing mode.
# endif
//右移3位 shiftcls_and_sig 相当于是 shiftcls 不同架构下叫法不同。位数不同。
shiftcls_and_sig = signedCls >> 3;
#elif SUPPORT_INDEXED_ISA //32位 armv7k or arm64_32
//32位下直接赋值,这里对应 nonpointer 为 0 的情况,为1的情况在外层。
cls = newCls;
#else // Nonpointer isa, no ptrauth
//x86_64 44位 nonpointer 是否开启都走这里
//右移3位。这里右移3位所以还原的时候需要左移3位 赋值给shiftcls
shiftcls = (uintptr_t)newCls >> 3;
#endif
}
-
arm64e
/模拟器下会根据ISA_SIGNING_SIGN_MODE
做判断和操作,最终cls >> 3
赋值给shiftcls_and_sig
。 -
32
为架构下并且nonpointer
为0
的时候直接赋值给isa_t
的cls
。 -
64
位下直接>>3
位无论nonpointer
为何值。
4.3 cls->classArrayIndex()
classArrayIndex
源码如下:
unsigned classArrayIndex() {
return bits.classArrayIndex();
}
unsigned classArrayIndex() {
#if SUPPORT_INDEXED_ISA
return data()->index;
#else
return 0;
#endif
}
可以看到取的是data()->index
。
setClassArrayIndex
逻辑如下:
void setClassArrayIndex(unsigned Idx) {
// 0 is unused as then we can rely on zero-initialisation from calloc.
ASSERT(Idx > 0);
data()->index = Idx;
}
调用者是objc_class::chooseClassArrayIndex
chooseClassArrayIndex
void objc_class::chooseClassArrayIndex()
{
#if SUPPORT_INDEXED_ISA
Class cls = (Class)this;
runtimeLock.assertLocked();
if (objc_indexed_classes_count >= ISA_INDEX_COUNT) {
// No more indexes available.
ASSERT(cls->classArrayIndex() == 0);
cls->setInstancesRequireRawIsaRecursively(false/*not inherited*/);
return;
}
unsigned index = objc_indexed_classes_count++;
if (index == 0) index = objc_indexed_classes_count++; // index 0 is unused
classForIndex(index) = cls;
cls->setClassArrayIndex(index);
#endif
}
可以看到index
来自objc_indexed_classes_count
,chooseClassArrayIndex
是在_read_images
的时候调用的。
objc_indexed_classes_count
objc_indexed_classes_count
的声明在objc-gdb.h
中:
// When we don't have enough bits to store a class*, we can instead store an
// index in to this array. Classes are added here when they are realized.
// Note, an index of 0 is illegal.
OBJC_EXPORT uintptr_t objc_indexed_classes_count;
大概意思是类被加载的时候回存入数据,这个值就是记录数组数量的。通过objc_indexed_classes_count
就能在数组中找到类(按image
分组)。
所以indexcls
存储的是类的索引。
4.4 isa关联类流程
isa关联cls总结
-
arm64e/模拟器
:- 1.根据
ISA_SIGNING_SIGN_MODE
操作cls
。 - 2.
shiftcls_and_sig = signedCls >> 3
,右移3
位赋值给shiftcls_and_sig
。
- 1.根据
-
64
位:shiftcls = (uintptr_t)newCls >> 3
,右移3
位赋值给shiftcls
。 -
32
位:-
nonpointer
为假:cls = newCls
直接赋值给cls
。 -
nonpointer
为真:indexcls = (uintptr_t)cls->classArrayIndex()
记录index
。
-
⚠️64位下无论开不开启nonpointer
,cls
都会右移3
位进行赋值。
BitsTest Demo