iOS 底层 day11 runtime isa详解
一般语言通过
编译链接
之后,代码的执行效果已经确定。但是 OC 可以通过Runtime API
动态修改程序执行效果,比如让[person run]
运行时变成[cat eat]
。Runtime API
提供的接口基本是 C 语言的,源码由C/C++/汇编
语言编写,源代码是开源的。
一、本节的主题是由浅入深,深入了解 isa
1. isa
的历史演变
- 在
arm64
之前,isa
是一个普通的指针,存储着Class
、Meta-Class
对象的内存地址 - 在
arm64
之后,苹果对isa
进行了优化,变成了一个共用体
(union
),还是用位域来存储更多的信息
2. 将 isa
之前,我们来解决一些看似无关的问题 ,下面 person对象
有三个数据需要存储tall
、rich
、handsome
,思考 person对象的三个数据
在内存中实际占据多大?再思考如何让 person对象的三个数据
仅占用一个字节?
#import <Foundation/Foundation.h>
@interface Person : NSObjec
@property(nonatomic, assign) BOOL tall;
@property(nonatomic, assign) BOOL rich;
@property(nonatomic, assign) BOOL handsome;
@end
- 三个 BOOL 类型的属性,所以一共占 3 个字节
- 如何让
person对象的三个数据
仅占用一个字节?下面介绍三种方法,由浅入深
3. 让 person对象的三个数据
仅占用一个字节的方案一
#import "Person.h"
// 掩码
#define SPTallMask (1<<0)
#define SPRichMask (1<<1)
#define SPHandsomeMask (1<<2)
@implementation Person
{
char _tallRichHandsome; // 仅占用一字节
}
// tall
- (void)setTall:(BOOL)tall{
if (tall) {
_tallRichHandsome = _tallRichHandsome | SPTallMask;
} else {
_tallRichHandsome = _tallRichHandsome & (~SPTallMask);
}
}
- (BOOL)tall{
return !!(_tallRichHandsome & SPTallMask);
}
// rich
- (void)setRich:(BOOL)rich{
if (rich) {
_tallRichHandsome = _tallRichHandsome | SPRichMask;
} else {
_tallRichHandsome = _tallRichHandsome & (~SPRichMask);
}
}
- (BOOL)rich{
return !!(_tallRichHandsome & SPRichMask);
}
// handsome
- (void)setHandsome:(BOOL)handsome{
if (handsome) {
_tallRichHandsome = _tallRichHandsome | SPHandsomeMask;
} else {
_tallRichHandsome = _tallRichHandsome & (~SPHandsomeMask);
}
}
- (BOOL)handsome{
return !!(_tallRichHandsome & SPHandsomeMask);
}
@end
- 如上程序,数据部分,仅仅是
char _tallRichHandsome;
所以仅仅占用一个字节,就实现了分别存储tall
、rich
、handsome
信息
4. 让 person对象的三个数据
仅占用一个字节的方案二(优化版
),使用结构体的位域实现
#import "Person.h"
@implementation Person
{
struct {
char tall : 1;
char rich : 1;
char handsome : 1;
} _tallRichHandsome;
}
// tall
- (void)setTall:(BOOL)tall{
_tallRichHandsome.tall = tall;
}
- (BOOL)tall{
return !!_tallRichHandsome.tall;
}
// rich
- (void)setRich:(BOOL)rich{
_tallRichHandsome.rich = rich;
}
- (BOOL)rich{
return !!_tallRichHandsome.rich;
}
// handsome
- (void)setHandsome:(BOOL)handsome{
_tallRichHandsome.handsome = handsome;
}
- (BOOL)handsome{
return !!_tallRichHandsome.handsome;
}
@end
- 上述代码结构体中,实际用到的是
3bit
,由于内存存储是以byte
为单位,所以也占用1byte
5. 让 person对象的三个数据
仅占用一个字节的方案 三(优化版
),使用共用体+结构体的位域实现
#import "Person.h"
// 掩码
#define SPTallMask (1<<0)
#define SPRichMask (1<<1)
#define SPHandsomeMask (1<<2)
@implementation Person
{
union {
char bits;
struct {
char tall : 1;
char rich : 1;
char handsome : 1;
};
} _tallRichHandsome;
}
// tall
- (void)setTall:(BOOL)tall{
if (tall) {
_tallRichHandsome.bits = _tallRichHandsome.bits | SPTallMask;
} else {
_tallRichHandsome.bits = _tallRichHandsome.bits & (~SPTallMask);
}
}
- (BOOL)tall{
return !!(_tallRichHandsome.bits & SPTallMask);
}
// rich
- (void)setRich:(BOOL)rich{
if (rich) {
_tallRichHandsome.bits = _tallRichHandsome.bits | SPRichMask;
} else {
_tallRichHandsome.bits = _tallRichHandsome.bits & (~SPRichMask);
}
}
- (BOOL)rich{
return !!(_tallRichHandsome.bits & SPRichMask);
}
// handsome
- (void)setHandsome:(BOOL)handsome{
if (handsome) {
_tallRichHandsome.bits = _tallRichHandsome.bits | SPHandsomeMask;
} else {
_tallRichHandsome.bits = _tallRichHandsome.bits & (~SPHandsomeMask);
}
}
- (BOOL)handsome{
return !!(_tallRichHandsome.bits & SPHandsomeMask);
}
@end
- 方案三,使用
联合体
+结构体位域
我们也达到同样的效果 - 这里的
struct
更多的是起了一个说明作用,说明bits
中的哪些数据代表tall
、rich
、handsome
6. 接下来我们看一下 isa
指针的具体源码,是否一下子就理解其中含义了呢?
union isa_t
{
Class cls;
uintptr_t bits;
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
struct {
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)
};
}
-
arm64
架构下,isa_t
共用体占据8 字节
,也就是64bit
, struct 中的内容就是对64bit
每位上代表的含义进行说明 - 其中
shiftcls
就是我们真正的指针地址,这也解答了,为什么我们在获得 instance 对象的 isa 地址值之后,需要isa地址值& ISA_MASK
才能获取真正的Class
或者Meta-Class
地址 -
uintptr_t shiftcls : 33;
前面有三个 bit 被占用,结合ISA_MASK
前三位为零,从而我们可以推敲出,无论是 Class 还是 Meta-Class 他们的地址值,前三位都是零,这个可以在项目中打印 Class 或 Meta-Class 的地址值可以得到验证。
7. 打印了 personClass(类对象) 和 personMetaClass(元类对象)
Demo[5657:409869] personClass : 0x100003278, personMetaClass: 0x100003250
Demo[5657:409869] objClass : 0x7fff99c5c118,objMetaClass : 0x7fff99c5c0f0
Demo[5657:409869] 常量区:0x1000032b0,堆区:0x100424b70,栈区:0x7ffeefbff55c
-
上述代码,我们可以验证今天所学,所有 Class 或者 MetaClass,后三位 bit 上的值都为零,在十六进制下的表现就是 0 或 8
-
然后我定义了三个确定存放在
数据区
,堆区
,栈区
的变量地址 -
我们观察可以发现
Person
的元类对象和类对象
,都存放在常量区
。 -
但是为什么
NSObjec
t 的元类对象和类对象
存放在栈区之上的区域
呢? -
目前只能猜测,栈区之上还有一部分给苹果官方用的
保留区
,存放的NSObject
的元类对象和类对象
-
如果有读者明白其中原理,请留言告知,非常感谢。
8. 部分 isa_t
共用体含义
-
nonpointer
: -
0,代表普通的指针,存储在 Class、Meta-Class 对象的内存地址
-
1,代表优化过,使用位域存储更多的信息
-
has_assoc
: -
是否有设置过关联对象,如果没有,释放时更快
-
has_cxx_dtor
: -
是否有 C++的析构函数(.cxx_destruct) ,如果没有,释放时更快
-
shiftcls
: -
存储着 Class、Meta-Class 对象的内存地址信息
-
magic
: -
用于调试时分辨对象是否未完成初始化
-
weakly_referenced
: -
是否有被弱引用指向过,如果没有,释放时会更快
-
deallocating
: -
对象是否正在释放
-
extra_rc
: -
里面存储的值是引用计数器减一
-
has_sidetable_rc
: -
引用计数器是否过大无法存储在 isa 中
-
如果为 1,那么引用计算会存储在一个叫 SideTable 的类的属性中