文章目录
- iOS - Runtime-isa详解(位域、union(共用体)、位运算)
- 前言
- 1. `位域`介绍
- 1.1 思路
- 1.2 示例 - 结构体
- 1.3 示例 - union(共用体)
- 1.3.1 说明
- 1.4 结构体 对比 union(共用体)
- 2. arm64架构对isa的优化
- 2.1 位域内容
- nonpointer
- has_assoc
- has_cxx_dtor
- shiftcls
- magic
- weakly_referenced
- deallocating
- extra_rc
- has_sidetable_rc
- 2.2 Class、Meta-Class对象存储位置
- 3. 拓展
- 3.1 枚举值设计
- 3.1.1 案例
- 3.1.2 原理分析
iOS - Runtime-isa详解(位域、union(共用体)、位运算)
前言
本章主要了解Runtime相关内容,苹果对isa
做了哪些优化,位域、union(共用体)又是如何运用的
- 要想学习Runtime,首先要了解它底层的一些常用数据结构,比如isa指针
- 在arm64架构之前,isa就是一个普通的指针,存储着
Class
、Meta-Class
对象的内存地址 - 从arm64架构开始,对isa进行了优化,变成了一个
共用体(union)
结构,还使用位域
来存储更多的信息
1. 位域
介绍
利用好位域
的话,可以使程序运行高效
、节省内存
,操作系统级别的东西很多都会使用
在IM系统的开发中,就使用了
位域
来对数据进行优化,后续有时间再抽出案例聊聊。
欢迎点赞,收藏,加关注!,谢谢你!!
1.1 思路
假如ZSXPerson
类需要3个BOOL类型的属性,这时候我们通常会直接使用@property
声明3个属性,这时候系统会给我们生成 3个_
开头的成员变量,3对get
、set
方法,所占据的内存也比较多
思路:BOOL 类型属性值要么 YES
要么 NO
,使用字节
中的一个位
(0或者1)其实就能表示一个 BOOL 类型的属性值,一个字节
就可以表示8
个 BOOL 值
1.2 示例 - 结构体
ZSXPerson.h
@interface ZSXPerson : NSObject- (void)setTall:(BOOL)tall;- (BOOL)isTall;- (void)setRich:(BOOL)rich;- (BOOL)isRich;- (void)setHandsome:(BOOL)handsome;- (BOOL)isHandsome;@end
ZSXPerson.m
@interface ZSXPerson() {struct {char tall: 1;char rich: 1;char handsome: 1;} _tallRichHandsome;
}@end@implementation ZSXPerson- (void)setTall:(BOOL)tall {_tallRichHandsome.tall = tall;
}- (BOOL)isTall {return !!_tallRichHandsome.tall;
}- (void)setRich:(BOOL)rich {_tallRichHandsome.rich = rich;
}- (BOOL)isRich {return !!_tallRichHandsome.rich;
}- (void)setHandsome:(BOOL)handsome {_tallRichHandsome.handsome = handsome;
}- (BOOL)isHandsome {return !!_tallRichHandsome.handsome;
}@end
main.m
int main(int argc, const char * argv[]) {@autoreleasepool {ZSXPerson *person = [[ZSXPerson alloc] init];person.tall = NO;person.rich = YES;person.handsome = YES;NSLog(@"tall:%d rich:%d handsome:%d", person.isTall, person.isRich, person.isHandsome);}return 0;
}
运行结果:
1.3 示例 - union(共用体)
ZSXPerson.h
@interface ZSXPerson : NSObject- (void)setTall:(BOOL)tall;- (BOOL)isTall;- (void)setRich:(BOOL)rich;- (BOOL)isRich;- (void)setHandsome:(BOOL)handsome;- (BOOL)isHandsome;@end
ZSXPerson.h.m
#import "ZSXPerson.h"#define ZSXTallMask (1)
#define ZSXRichMask (1 << 1)
#define ZSXHandsomeMask (1 << 2)@interface ZSXPerson() {union {char bits;struct {char tall: 1;char rich: 1;char handsome: 1;};}_tallRichHandsome;
}@end@implementation ZSXPerson- (void)setTall:(BOOL)tall {if (tall) {_tallRichHandsome.bits |= ZSXTallMask;}else {_tallRichHandsome.bits &= ~ZSXTallMask;}
}- (BOOL)isTall {return !!(_tallRichHandsome.bits & ZSXTallMask);
}- (void)setRich:(BOOL)rich {if (rich) {_tallRichHandsome.bits |= ZSXRichMask;}else {_tallRichHandsome.bits &= ~ZSXRichMask;}
}- (BOOL)isRich {return !!(_tallRichHandsome.bits & ZSXRichMask);
}- (void)setHandsome:(BOOL)handsome {if (handsome) {_tallRichHandsome.bits |= ZSXHandsomeMask;}else {_tallRichHandsome.bits &= ~ZSXHandsomeMask;}
}- (BOOL)isHandsome {return !!(_tallRichHandsome.bits & ZSXHandsomeMask);
}@end
main.m
int main(int argc, const char * argv[]) {@autoreleasepool {ZSXPerson *person = [[ZSXPerson alloc] init];person.tall = NO;person.rich = YES;person.handsome = YES;NSLog(@"tall:%d rich:%d handsome:%d", person.isTall, person.isRich, person.isHandsome);}return 0;
}
运行结果:
1.3.1 说明
1.4 结构体 对比 union(共用体)
结构体
的成员是各自占用各自所需大小共同体
的内存大小取决于其中最大的成员的大小,所有成员共用这块内存
- 使用
共用体
实际上还是通过位运算
来控制每个属性所占位置 - 其中的
sturct
目的是增加可读性,实际上不会影响属性所占位置
2. arm64架构对isa的优化
arm64
架构对isa
中,使用一个64位
的共用体来存储更多的信息,通过位域
的概念来表示各个存储的信息的存储位置。其中有33位
拿来存储Class、Meta-Class
的地址值
2.1 位域内容
nonpointer
- 0,代表普通的指针,存储着Class、Meta-Class对象的内存地址
- 1,代表优化过,使用位域存储更多的信息
has_assoc
- 是否有设置过关联对象,如果没有,释放时会更快
has_cxx_dtor
- 是否有C++的析构函数(.cxx_destruct),如果没有,释放时会更快
shiftcls
- 存储着Class、Meta-Class对象的内存地址信息
magic
- 用于在调试时分辨对象是否未完成初始化
weakly_referenced
- 是否有被弱引用指向过,如果没有,释放时会更快
deallocating
- 对象是否正在释放
extra_rc
- 里面存储的值是引用计数器减1
has_sidetable_rc
- 引用计数器是否过大无法存储在isa中
- 如果为1,那么引用计数会存储在一个叫SideTable的类的属性中
2.2 Class、Meta-Class对象存储位置
Class、Meta-Class对象存储在shiftcls
,从上图可知shiftcls
是从第4
位开始,连续33
位
isa的掩码:define ISA_MASK 0x0000000ffffffff8ULL
将掩码转为二进制查看
它表示的就是从第4
位开始,连续33
位,通过这个掩码
,就可以将Class、Meta-Class
的地址值
取出来
Class、Meta-Class
的地址值
后三位永远是 0。因为他的掩码左右边3
位是 0,&
运算后一定是 0
3. 拓展
3.1 枚举值设计
在iOS中,系统的一些API可以使用|
传入多个枚举值,比如:
self.view.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleRightMargin;
原理就是使用位域
设计枚举值,然后通过位运算
来取值
3.1.1 案例
我们也自己来设计一个这样的枚举
定义枚举:
typedef NS_OPTIONS(NSUInteger, ZSXOptions) {ZSXOptions1 = 1 << 0, // 0b00000001ZSXOptions2 = 1 << 1, // 0b00000010ZSXOptions3 = 1 << 2, // 0b00000100ZSXOptions4 = 1 << 3, // 0b00001000ZSXOptions5 = 1 << 4, // 0b00010000
};
设置值方法:
- (void)setOptions:(ZSXOptions)options {if (options & ZSXOptions1) {NSLog(@"包含了ZSXOptions1");}if (options & ZSXOptions2) {NSLog(@"包含了ZSXOptions2");}if (options & ZSXOptions3) {NSLog(@"包含了ZSXOptions3");}if (options & ZSXOptions4) {NSLog(@"包含了ZSXOptions4");}if (options & ZSXOptions5) {NSLog(@"包含了ZSXOptions5");}
}
使用:
[self setOptions:ZSXOptions1 | ZSXOptions3 | ZSXOptions5];
打印如下:
此时我们已经实现了一个可以传入多个枚举值的接口
3.1.2 原理分析
/**0b000000010b000001000b00010000----------------- | 运算(设置值)0b000101010b00000001----------------- & 运算(取值)0b00000001 为 true*/
@oubijiexi