iOS底层-Runtime系列一
简介
- Objective-C是一门动态性比较强的编程语言,跟C、C++等语言有着很大的不同
- Objective-C的动态性是由Runtime API来支撑的
- Runtime API提供的接口基本都是C语言的,源码由C\C++\汇编语言编写
isa
位域
-
代码示例: Person类
@interface Person : NSObject @property (nonatomic, assign) BOOL tall; @property (nonatomic, assign) BOOL rich; @property (nonatomic, assign) BOOL handsome; @end
- 如果当前Person使用3个BOOL属性,那么这3个属性将占据3个字节,那么我们可以不可以用一个字节来表达这3个属性的值呢?以便于节省内存
- 可以申请一个char成员变量,占据1个字节(8个二进制位),可以指定其中的某3个二进制位分别表示3个属性的值,比如后三位: 0b 0000 0111
- 那么如何取出相应的二进制位呢?
- 使用二进制按位与运算:
&
- 使用二进制按位与运算:
- 如何设置相应的二进制位呢?
- 使用二进制按位或运算:
|
- 使用二进制按位或运算:
-
优化代码:
@interface Person : NSObject - (void)setTall:(BOOL) tall; - (BOOL)isTall; - (void)setRich:(BOOL) rich; - (BOOL)isRich; - (void)setHandsome:(BOOL) handsome; - (BOOL)isHandsome; @end //Mask掩码,一般用来与&运算 #import "Person.h" //#define TallMask 1 //#define RichMask 2 //#define HandsomeMask 4 //#define TallMask 0b00000001 //#define RichMask 0b00000010 //#define HandsomeMask 0b00000100 #define TallMask (1<<0) //1左移0位 #define RichMask (1<<1) //1左移1位 #define HandsomeMask (1<<2) //1左移2位 @interface Person () { char _tallRichHandsome;//二级制后3位分别代表:handsome、rich、tall, 0b 0000 0111 } @end @implementation Person - (instancetype)init { self = [super init]; if (self) { _tallRichHandsome = 0b00000111; } return self; } - (void)setTall:(BOOL) tall{ if (tall) { _tallRichHandsome |= TallMask; }else{ _tallRichHandsome &= ~TallMask; } } - (BOOL)isTall{ return !!(_tallRichHandsome & TallMask); } - (void)setRich:(BOOL) rich{ if (rich) { _tallRichHandsome |= RichMask; }else{ _tallRichHandsome &= ~RichMask; } } - (BOOL)isRich{ return !!(_tallRichHandsome & RichMask); } - (void)setHandsome:(BOOL) handsome{ if (handsome) { _tallRichHandsome |= HandsomeMask; // _tallRichHandsome= _tallRichHandsome | HandsomeMask }else{ //先将HandsomeMask按位取反,然后在于_tallRichHandsome,进行&运算 _tallRichHandsome &= ~HandsomeMask; } } /** 0000 0000 & 0000 1000 */ - (BOOL)isHandsome{ return !!(_tallRichHandsome & HandsomeMask); } @end //测试 Person *person = [[Person alloc] init]; [person setRich:NO]; NSLog(@"%d=%d=%d",person.isTall,person.isRich,person.isHandsome);
-
使用结构体的位域功能
#import "Person.h" @interface Person () { //定义一个结构体类型成员变量 //结构体支持位域技术 struct{ char tall : 1; //代表只占用1个二进制位,不用管左边的char类型,以右边的 :1 为准 char rich : 1; char handsome : 1; }_tallRichHandsome;//变量整体占用一个字节:0b00000000,第一个成员在最后一位,往下依次 } @end @implementation Person - (void)setTall:(BOOL) tall{ _tallRichHandsome.tall = tall; } - (BOOL)isTall{ //如果tall值为1,为何返回为-1? //_tallRichHandsome.tall是一个二进制位1,而返回值是BOOL,如果强制转换为BOOL,那么它会将剩余的7位全部补为1 //就变成: 0b 1111 1111 // return _tallRichHandsome.tall; //2次取反可以解决这个问题: !_tallRichHandsome.tall 返回值一定是一个BOOL,非0即真。 再次!就是原值0或1 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 //使用 Person *person = [[Person alloc] init]; [person setRich:YES]; [person setHandsome:YES]; //0=-1=-1 为何是-1呢? NSLog(@"%d=%d=%d",person.isTall,person.isRich,person.isHandsome);
联合体、结构体位域表示
-
示例:
#import "Person.h" @interface Person () { //联合体成员_tallRichHandsome union{ char bits;//占据一个字节 //增加可读性,告诉bits这个字节数据哪些二进制位表示什么 struct{ char tall : 1; //bits二进制位最后一位是tall char rich : 1; //bits二进制位倒数第二位是rich char handsome : 1;//bits二进制位倒数第三位是handsome }; }_tallRichHandsome; //等价 // char _tallRichHandsome; } @end @implementation Person - (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
- 分析:
- 上面联合体成员变量_tallRichHandsome,等价于直接写一个 _tallRichHandsome成员
char _tallRichHandsome;
,那么为何偏要用一个union类型的呢? - 因为如果仅仅是
char _tallRichHandsome;
成员变量,那么如何告诉别人这个字节的8位中哪一位分别代表什么呢? - 联合体union的内存公用技术,结构体的struct位域技术,同时可以满足:不占用多余内存,又能直接说明二进制位每一位都代表什么
- union内部尽管有2个成员:一个是bits,一个是无名称的struct,bits是char类型占据1个字节,struct仅仅只是3个二进制位,内存对齐后则也是1个字节,因此union类型的成员整体还是占用一个字节
- union中的无名的struct成员,是什么作用呢?(重点!!!!经典!!!)
- 增加可读性,告诉bits这个字节数据哪些二进制位表示什么
- struct无名,可以直接通过联合体对象取出struct的成员,比如:
_tallRichHandsome.handsome;
-
可以直接通过struct的成员访问修改bits的二进制位
- 无名struct和bits公用一块内存,而且struct采用位域技术使得直接可以通过struct的成员访问修改到相应二进制位的数据
- 那么也就是相应修改了bits的数据
- 这就是使用union与无名struct结合的经典之处!!!
- 直接可以通过struct的成员修改bits相应的二进制位
- 上面联合体成员变量_tallRichHandsome,等价于直接写一个 _tallRichHandsome成员
- 分析:
isa详解
- 要想学习Runtime,首先要了解它底层的一些常用数据结构,比如isa指针
- 在arm64架构之前,isa就是一个普通的指针,存储着Class、Meta-Class对象的内存地址
- 从arm64架构开始,对isa进行了优化,变成了一个共用体(union)结构,还使用位域来存储更多的信息
-
arm64 isa类型源码如下:
union isa_t { // 构造函数 isa_t() { } isa_t(uintptr_t value) : bits(value) { } Class cls; uintptr_t bits; #if SUPPORT_NONPOINTER_ISA # if __arm64__ # define ISA_MASK 0x0000000ffffffff8ULL # define ISA_MAGIC_MASK 0x000003f000000001ULL # define ISA_MAGIC_VALUE 0x000001a000000001ULL struct { uintptr_t indexed : 1; uintptr_t has_assoc : 1; uintptr_t has_cxx_dtor : 1; uintptr_t shiftcls : 33; 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 struct { uintptr_t indexed : 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 // Available bits in isa field are architecture-specific. # error unknown architecture # endif // SUPPORT_NONPOINTER_ISA #endif };
-
分别对arm64、x86(真机、模拟器)做了适配你,如果仅分析真机,简化如下:
//从C++角度分析,就相当于一个联合体类,类似于结构体类 union isa_t { // 构造函数 isa_t() { } //构造函数2,同时具有初始化列表 isa_t(uintptr_t value) : bits(value) { } Class cls; uintptr_t bits; # define ISA_MASK 0x0000000ffffffff8ULL //取33位1 # define ISA_MAGIC_MASK 0x000003f000000001ULL # define ISA_MAGIC_VALUE 0x000001a000000001ULL struct { uintptr_t indexed : 1; uintptr_t has_assoc : 1; uintptr_t has_cxx_dtor : 1; uintptr_t shiftcls : 33; 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) }; };
- 从上面可以看出bits成员存放了许多信息,struct中描述的信息
- struct描述了每个信息占据多少二进制位
- 上面的ULL的意思是无符号长整型(unsigned long long),占据64位
- ISA_MASK宏与bits进行&运算,可以取出bits中(从右往左)第4位开始的33位二进制的数据
-
- struct位域 分别表示什么
- nonpointer
- 0,代表普通的指针,存储着Class、Meta-Class对象的内存地址
- 1,代表优化过,使用位域存储更多的信息
- 这是什么意思呢?
- isa是isa_t的联合体类型,内存占据大小8个字节64位
- 如果nonpointer为0,也就是64位的最后一位为0,就代表当前的8个字节存储的仅仅是Class或者Meta-Class对象的内存地址,
- 不存在无名结构体中的复杂信息
- 反之,如果nonpointer为1,说明这8个字节存储的是无名结构体中的复杂信息,cls成员无用
- 总结:
- isa_t可以看成是只有2个成员(因为无名结构体是用来说明bits各个位的内容以及取值、设置bits各个位的内容的)clas与bits
- 而这2个成员共用一块8个字节的内存,那么如何判断这个内存中存的是clas还是bits呢?
- 就看这个内存中最后一位(地址最低位)即nonpointer位,是1还是0.如果是1,该内存存储的是bits,如果是0该内存存储的是clas,即Class或者Meta-Class对象的内存地址
- has_assoc
- 是否有设置过关联对象,如果没有,释放时会更快
- has_cxx_dtor
- 是否有C++的析构函数(.cxx_destruct),如果没有,释放时会更快
- shiftcls
- 存储着Class、Meta-Class对象的内存地址信息
- 那么想要获得内存地址:ISA_MASK & bits
- (真机)从ISA_MASK可以看出,一个对象的类对象、元类对象的地址值,最后3位二进制位一定是0
- magic
- 用于在调试时分辨对象是否未完成初始化
- weakly_referenced
- 是否有被弱引用指向过,如果没有,释放时会更快
- deallocating
- 对象是否正在释放
- extra_rc
- 里面存储的值是引用计数器减1
- has_sidetable_rc
- 引用计数器是否过大无法存储在isa中
- 如果为1,那么引用计数会存储在一个叫SideTable的类的属性中
- nonpointer
位运算补充
-
我们常看见苹果系统中有些方法设置枚举参数可以使用
|
运算符连接,是怎么回事呢?self.view.autoresizingMask= UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleWidth;
-
举例使用:
//typedef enum { // ZHOptionsOne = 1, //0b00000001 // ZHOptionsTwo = 2,//0b00000010 // ZHOptionsThree = 4//0b00000100 //} ZHOptions; //或者 typedef enum { ZHOptionsOne = 1<<0, //0b00000001 ZHOptionsTwo = 1<<1,//0b00000010 ZHOptionsThree = 1<<2//0b00000100 } ZHOptions; @interface ViewController () @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; //用或运算 | 设置多种类型 [self setOptions:ZHOptionsOne|ZHOptionsTwo]; } //用与运算 & 解析多种类型 -(void)setOptions:(ZHOptions)options{ if (options & ZHOptionsOne) { NSLog(@"包含了ZHOptionsOne"); } if (options & ZHOptionsTwo) { NSLog(@"包含了ZHOptionsTwo"); } if (options & ZHOptionsThree) { NSLog(@"包含了ZHOptionsThree"); } }
总结
- 苹果为何将isa这个本来是Class类型的指针(objc_class的结构体指针),现在换成isa_t联合体类型的成员了呢?
- isa占据内存大小不变:以前是Class类型占据8个字节,现在是isa_t联合体类型也占据8个字节(各个成员变量共用)
- isa_t联合体类型存储的东西更多,更加节省内存,可以用一个64位二进制保存对象的所有信息,包括原来Class指针指向的地址
- isa_t联合体类型完全兼容原来的Class类型,只要最后一位为0,此时的isa_t联合体类型存储的就是Class类型;是1,联合体类型存储的就是bits