前言
了解isa
之前首先看一下Clang
-
Clang
是一个由Apple
主导编写,基于LLVM
的C/C++/Objective-C
编译器 - 作用:借助
Clang
可以将oc
文件输出成C++
文件,方便探究其底层的一些结构
、逻辑
、底层
的实现原理等。
正文
探索对象
- 在
main.m
中有个一类Person
,当中有一个属性name
@interface Person : NSObject
@property (nonatomic , copy) NSString *name;
@end
@implementation Person
@end
通过Clang
编译main.m
,编译成main.cpp
//没有依赖的情况下 main.m
clang -rewrite-objc main.m -o main.cpp
//存在依赖的情况下编译 ViewController.m
clang -rewrite-objc -fobjc-arc -fobjc-runtime=ios-13.0.0 -isysroot / /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator13.7.sdk ViewController.m
分析编译结果main.cpp
当中发现Person被编译成了一个结构体
它的第一个属性就是isa
伪
struct NSObject_IMPL {
Class isa;
};
struct Person_IMPL {
struct NSObject_IMPL NSObject_IVARS;
NSString *_name;
};
///name
static NSString * _I_Person_name(Person * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_Person$_name)); }
///属性赋值
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);
///setName
static void _I_Person_setName_(Person * self, SEL _cmd, NSString *name) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct Person, _name), (id)name, 0, 1); }
可以发现在 NSObject_IMPL
结构体中isa
类型变成了class
我们在探索alloc&init&new当中有提到isa
的初始化关联过程
-
isa
相关
联合体(union
)一种特殊的数据类型,
允许在相同的内存位置存储不同的数据类型。
所有成员占用同一段内存
,修改一个成员会影响其余所有成员
。
共用体使用了内存覆盖技术,同一时刻只能保存一个成员的值
,如果对新的成员赋值,就会把原来成员的值覆盖掉
。
各变量是互斥
的——缺点就是不够包容; 但优点是内存使用更为精细灵活
,也节省了内存空间
。
共用体占用的内存等于最长的成员占用的内存
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
};
#endif
};
所以苹果为了节省内存空间,这里使用了联合体的形式。
这里就是采用了联合体&位域
的方式,我们来看看位域里边都存储了些什么
// arm64 真机
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
# 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 deallocating : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 19
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
// x86_64 模拟器
# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
# define ISA_MAGIC_MASK 0x001f800000000001ULL
# define ISA_MAGIC_VALUE 0x001d800000000001ULL
# 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 deallocating : 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
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。
if (!zone && fast) {
obj->initInstanceIsa(cls, hasCxxDtor);
} else {
// Use raw pointer isa on the assumption that they might be
// doing something weird with the zone or RR.
obj->initIsa(cls);
}
- 转到
initIsa
initIsa
可以发现cls
类的信息就存储在isa
通过偏移3个字节存入shiftcls
接下来通过lldb

验证isa
1.通过位运算
验证
2.通过& ISA_mask
验证
第一种

因为
shiftcls
是偏移3个字节存入的 所以将isa地址右移3位:
p/x 0x001d800100002225 >> 3
,得到0x0003b00020000444
在将得到的0x0003b00020000444
左移20位:p/x 0x0003b00020000444 << 20
,得到0x0002000044400000
为什么是左移20
位?因为先右移了3
位,相当于向右偏移了3
位,而左边需要抹零的位数有17
位,所以一共需要移动20
位
将得到的0x0002000041d00000
再右移17位:po 0x0002000041d00000 >> 17
得到person
第二种

查看2进制下的
0x0000000ffffffff8ULL

所以 字节
&
操作相当与摸出后3位和前面的字节得到
person
网友评论