目录
- isa结构
----nonpointer
----has_assoc
----has_cxx_dtor
----shiftcls
----magic
----weakly_referenced
----has_sidetable_rc
----extra_rc
----deallocating
- 位运算
----位运算简介
----通过位运算实现get方法
----通过位运算实现set方法- 位域
- 共用体
- KVO参与时的位运算
- 总结
isa结构
在
arm64
结构之前,isa
就是一个普通的指针
。存储着Class
,Mata-Class
对象的内存地址。
从arm64
架构开始,对isa
进行了优化,变成了一个共用体(union)结构
,并使用位域
来存储更多的信息。
在objc源码的objc-private
文件中(arm64&真机环境
),经过简化之后isa
共用体结构如下:
// isa共用体(arm64&真机,简化后)
union isa_t {
uintptr_t bits;
Class cls;
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 unused : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 19
};
};
-
nonpointer
0,代表普通的指针,存储着Class、Meta-Class对象的内存地址
1,代表优化过,使用位域存储更多的信息
-
has_assoc
是否有设置过关联对象,如果没有,释放时会更快
-
has_cxx_dtor
是否有C++的析构函数(.cxx_destruct),如果没有,释放时会更快
-
shiftcls
存储着Class、Meta-Class对象的内存地址信息
-
magic
用于在调试时分辨对象是否未完成初始化
-
weakly_referenced
是否有被弱引用指向过,如果没有,释放时会更快
-
has_sidetable_rc
引用计数器是否过大无法存储在isa中
如果为1,那么引用计数会存储在一个叫SideTable的类的属性中
-
extra_rc
里面存储的值是引用计数器减1
-
deallocating
对象是否正在释放
位运算
-
位运算简介
使用位运算是为了节省空间,比如一个
Bool
类型变量占用一个字节内存,也就是8位(ob代表二进制,如:0b 0000 0101)
,每一位都是由0和1
组成。
那么如果每一位都能存储一个Bool
信息的话,就不用创建多个Bool变
量来存储信息了,也就节省了内存。
比如创建一个Person
类,添加高、富、帅
三个属性,这样三个属性占用了三个字节
。是否可以把三个字节的信息节省为1个字节?
@interface Person : NSObject
@property (nonatomic, assign) BOOL tall;
@property (nonatomic, assign) BOOL rich;
@property (nonatomic, assign) BOOL handsome;
@end
可以用位运算实现,比如字节a和字节b通过位运算得到新的字节
0b 0000 0001 //字节a,最后一位是1
& 0b 0000 0011 //字节b,最后两位是1
----------------
0b 0000 0001 //字节c,最后一位是1
字节a和字节b通过位运算(&)
得到新的字节。
将字节里的每一位取出&(与)运算
,运算结果组成了新的字节c。
补充:位运算(&与),如果大家都是1,结果才是1,其他结果都是0
。
想把c转成bool类型可以进行!(取反)操作,比如0b 0000 0001转成10进制就是1,!(1)就是NO,!!(1)就是YES,即如果c有值(c != 0)那么!!c = YES,如果c没有值(c = 0)那么!!c = NO。
-
通过位运算实现get方法
Person
的三个属性tall
、rich
、handsome
就可以保存在一个字节的数据当中:
char _tallRichHandsome = 0b00000011;
用它的倒数第一位保存tall的信息,倒数第二位保存rich的信息,倒数第三位保存handsome的信息,三个属性的get方法可以写成:
#define TallMask (1<<0)
#define RichMask (1<<1)
#define HandsomeMask (1<<2)
- (BOOL)tall {
return !!(_tallRichHandsome & TallMask);
}
- (BOOL)rich {
return !!(_tallRichHandsome & RichMask);
}
- (BOOL)handsome {
return !!(_tallRichHandsome & HandsomeMask);
}
xxxxMask
一般指的是掩码,用于做位运算。
1<<2
代表1向左移动2位
,(0b 0000 0100)。
-
通过位运算实现set方法
要实现set方法,就需要更新_tallRichHandsome
指定位的值,但又不能改变其他位的值。
如果设置的值是YES,可以通过按位或运算实现:
0b 0000 0010 //字节a,倒数第二位是1
| 0b 0000 0001 //字节b,最后一位是1
----------------
0b 0000 0011 //字节d,最后两位是1
用0b 0000 0001和a
做或运算,得到新的字节d,既保留了a其它位的值,又更新了a最后一位的值为1。
如果设置的值是NO,可以通过按位与运算实现:
0b 0000 0010 //字节a,倒数第二位是1
& 0b 1111 1101 //字节b,倒数第二位是0
----------------
0b 0000 0000 //字节d,都是0
用0b 1111 1101和a
做或运算,得到新的字节d,既保留了a其它位的值,又更新了a最后倒数第二位的值为0。
Person的set方法可以写成:
- (void)setTall:(BOOL)tall {
if (tall) {
_tallRichHandsome |= TallMask;
}else {
_tallRichHandsome &= ~TallMask;
}
}
- (void)setRich:(BOOL)rich {
if (rich) {
_tallRichHandsome |= RichMask;
}else {
_tallRichHandsome &= ~RichMask;
}
}
- (void)setHandsome:(BOOL)handsome {
if (handsome) {
_tallRichHandsome |= HandsomeMask;
}else {
_tallRichHandsome &= ~HandsomeMask;
}
}
~
代表按位取反(~0b 0000 0100 = 0b 1111 1011)
。
位域
结构体中有位域的功能,可以设定结构体内部成员占几位,比如:
struct {
char tall : 1;
char rich : 1;
char handsome : 1;
} _tallRichHandsome;
冒号后面的数字表示
tall、rich、handsome
各占1位。在内存中写在上面的成员会在_tallRichHandsome
所占字节的最右边
。
利用位域可以优化Person中的set和get方法
- (void)setTall:(BOOL)tall {
_tallRichHandsome.tall = tall;
}
- (void)setRich:(BOOL)rich {
_tallRichHandsome.rich = rich;
}
- (void)setHandsome:(BOOL)handsome {
_tallRichHandsome.handsome = handsome;
}
- (BOOL)tall {
return !!_tallRichHandsome.tall;
}
- (BOOL)rich {
return !!_tallRichHandsome.rich;
}
- (BOOL)handsome {
return !!_tallRichHandsome.handsome;
}
共用体
共用体结构如下:
union {
char bits;
struct {
char tall : 1;
char rich : 1;
char handsome : 1;
};
} _tallRichHandsome;
共用体中
bits
和结构体
都共用1个字节
的内存。
bits
是负责保存各个属性的值
,使用位运算进行保存和读取。
结构体只是为了提高可读性,没有读写逻辑。
使用共用体Person的set和get方法可以修改为:
- (void)setHandsome:(BOOL)handsome {
if (handsome) {
_tallRichHandsome.bits |= HandsomeMask;
}else {
_tallRichHandsome.bits &= ~HandsomeMask;
}
}
- (BOOL)handsome {
return !!(_tallRichHandsome.bits & 4);//0b00000100
}
KVO参与时的位运算
在添加KVO时,配置参数可以通过按位或传入:
[self addObserver:self forKeyPath:@"Title" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
在内部拿到的是按位或运算的结果,如果在这个结果中找出传入了那些数据?
自定义一个枚举,和setOpitions方法:
typedef enum {
OptionsOne = 1<<0,
OptionsTwo = 1<<1,
OptionsThree = 1<<2
} Options;
- (void)setOpitions:(Options)options;
1<<2 代表1向前移动2位,即0b 0000 0100
观察OptionsOne、OptionsTwo、OptionsThree按位或运算的结果:
0b 0000 0001 // OptionsOne,最后一位是1
0b 0000 0010 // OptionsTwo,倒数第二位是1
| 0b 0000 0100 // OptionsThree,倒数第三位是1
----------------
0b 0000 0111 // options,最后一位是1
最后三个option的数据都保存在了options里面。
如果想从结果中判断是否包含某个option,可以使用按位与运算来判断:
0b 0000 0111 // options,最后三位是1
& 0b 0000 0001 // OptionsOne,最后一位是1
----------------
0b 0000 0001 // 结果,最后一位是1
可以发现,OptionsOne通过和options进行与运算结果还是OptionsOne,这样就能判断options是否包含某个元素,setOptions方法内部可以这样判断:
- (void)setOpitions:(Options)options {
if (OptionsOne & options) {
// 包含OptionsOne
}
if (OptionsTwo & options) {
// 包含OptionsTwo
}
if (OptionsThree & options) {
// 包含OptionsThree
}
}
总结
了解位运算、位域、共用体后,就可以知道isa共用体内的bits是用于保存各种信息的,struct是为了提高代码可读性,列举了isa保存的各种信息,isa共用体可以简写为:
// isa共用体
union isa_t {
uintptr_t bits;
Class cls;
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 unused : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 19
};
};
之前了解过过isa指针是通过&ISA_MASK之后才能得到Class对象的地址:

# define ISA_MASK 0x0000000ffffffff8ULL
将ISA_MASK转成2进制可以看出,从第3位开始总共有33个1,正好对应着isa共用体中的shiftcls,shiftcls存储了class的指针数据,也印证了class = isa&ISA_MASK。(可以发现类对象、元类对象地址值后三位都是0)。
Tips:正常无法拿到OC对象的isa指针地址,可以创建一个和OC对象底层结构一样的结构体,将OC对象转成结构体,再读取isa指针地址。
网友评论