iOS底层探索三-对象原理(下)
前言
一、 对象的本质
1、对象在源文件(.cpp)的底层实现
定义一个类:Person,为了方便搜索到Person在源文件(.cpp文件)中的位置,定义在main.m文件中。
执行clang
命令:clang -rewrite-objc main.m -o main.cpp
,将源代码转换成源文件。(如果源代码中包含UIKit头文件,需要添加路径)
或者使用xcrun
命令:xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp
,将源代码转换成源文件。(建议使用:可以减少很多路径配置)
- clang:是一个由Apple主导编写,基于LLVM的 C\C++\Objective-C的编译器
- xcrun:Xcode工具、-sdk:平台,iphoneos:真机,iphonesimulator:模拟器、 -arch:基于什么架构,苹果手机是基于arm64的、-rewrite-objc:重写oc文件,-o:输出到那个文件,文件名自定义。
底层实现:
源文件中Person 对象是以结构体的形式实现,并且 struct Person_IMP
嵌套 struct NSObject_IMPL
,NSObject_IMPL
的底层实现是一个isa指针。isa 是类的隐藏变量,是类的成员变量之一。
源文件中NSObject_IMPL
的底层实现:
2、对象类型的底层实现
NSObject:
在源代码中,Person类是继承于NSObject,而源文件(.cpp)中 Person类是objc_object类型,因而 NSObject 的本质是objc_object。
Class:
Class 类型是 objc_class 指针类型,objc_class 是一个结构体,因而Class的本质是结构体指针类型,id
和 SEL
在底层的本质也是结构体指针,类型分别是objc_object *
和 objec_selector *
, 因此在源代码中id 类型或者是Class类型,不需要添加 * 的原因。
二、isa
1、isa_t
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
};
复制代码
union isa_t
是 objc818源码中的实现。它是一个联合体类型,而联合体中的各个变量是互斥的。
2、联合体扩展
- 结构体(struct):结构体中所有变量是“共存”的
- 优点是“有容乃⼤”, 全⾯
- 内存空间的分配是粗放的,不管⽤不⽤,全分配
- 联合体(union):联合体中是各变量是“互斥”的
- 内存使⽤更为精细灵活,也节省了内存空间
- 缺点就是不够“包容”;
定义结构体(struct)Person1 和 联合体(union)Person2, 分别赋值输出:结构体(struct)类型person1元素可以分别赋值;联合体(union)person2元素只有最后一个元素赋值,因为联合体共用一块内存,当后一个元素赋值后,上一个元素的内存地址会被后一个元素持有。
3、ISA_BITFIELD
# 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)
# 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
// SUPPORT_PACKED_ISA
#endif
复制代码
arm64
和 x86_64
是两个不同的架构。arm64:iPhone,x86_64:模拟器。
nonpointer
:表示是否对 isa 指针开启指针优化;0:纯isa指针,1:不⽌是类对象地址,isa 中包含了类信息、对象的引⽤计数等(默认是:1)。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。
各个成员变量在isa_t
结构体中所在位域如下:
4 、位域(bit fields)扩展
位域是指信息在存储时,并不需要占用一个完整的字节, 而只需占几个或一个二进制位。例如在存放一个开关量时,只有0和1 两种状态, 用一位二进位即可。
定义结构体Struct1和指定位域结构体Struct2,计算两种类型结构体大小。Struct1类型4字节,Struct2类型1字节。
Struct1和Struct2位域储存:
5、shiftcls:存储类指针的值
三、对象地址如何关联到类地址
1、isa地址平移
定义类Person,移动person对象的isa地址得到只包含shiftcls的地址,对比得到和类信息地址相同
isa地址平移图解:
2、isa &= ISA_MASK
源码中isa的寻址逻辑并不是平移得到的,而是通过ISA_MASK(掩码)
得到
inline Class
isa_t::getClass(MAYBE_UNUSED_AUTHENTICATED_PARAM bool authenticated) {
#if SUPPORT_INDEXED_ISA
return cls;
#else
uintptr_t clsbits = bits;
# if __has_feature(ptrauth_calls)
# if ISA_SIGNING_AUTH_MODE == ISA_SIGNING_AUTH
if (authenticated) {
clsbits &= ISA_MASK;
if (clsbits == 0)
return Nil;
clsbits = (uintptr_t)ptrauth_auth_data((void *)clsbits, ISA_SIGNING_KEY, ptrauth_blend_discriminator(this, ISA_SIGNING_DISCRIMINATOR));
} else {
clsbits &= objc_debug_isa_class_mask;
}
# else
clsbits &= objc_debug_isa_class_mask;
# endif
# else
clsbits &= ISA_MASK;
# endif
return (Class)clsbits;
#endif
}
复制代码
按照源码方法 clsbits &= ISA_MASK
得到clsbits类信息:
ISA_MASK
:掩码,模拟器环境:低三位和高十七位抹掉;真机环境:低三位和高二十八位抹掉。
模拟器环境:对象地址64位位域[4-47]
位域存储类的信息,真机环境:对象地址64位位域[4-35]
位域存储类的信息
LLDB调试命令拓展
memory read
:memory read
的简写为x
,memory read xxx
读取当前对象在内存的中存储情况p/t
:二进制打印x/4gx
- x:读取内存的命令
- 4:连续打印4段内存
- g:进行格式化的输出打印 (iOS为小端,不进行格式化,输出的内容为:d0 c5 f8 0e 01 00 00 00 00 00 00 00 00 00 00 00 )
- x:16进制显示结果