一、实现逻辑
我们可以用简单的Bool类型,来实现数据在二进制中的取值和赋值:
1、取值
掩码 + 位与(&)运算。
/** * * 0000 0111 * & 0000 0010 *------------- * 0000 0010 * * 二进制转Bool类型需要两次取反(!!) */
2、赋值
2.1、当设置值为1时
掩码 + 位或(|)运算。
/** * 0000 0101 * | 0000 0010 *------------- * 0000 0111 */
2.2、当设置值为0时
掩码取反(~) + 再进行位与(&)运算。
/** * 0000 0111 * & 1111 1101 *------------- * 0000 0101 */
知道了位运算的基本规则,下面用几个例子,来了解一下位运算在OC中的应用:
二、位运算符赋值
这里我们就用高富帅(tall、rich、handsome)的取值和赋值方法,作为举例。
2.1、代码示例
Person.h
#import <Foundation/Foundation.h> @interface Person : NSObject - (void)setTall:(BOOL)tall; - (void)setRich:(BOOL)rich; - (void)setHandsome:(BOOL)handsome; // 高 - (BOOL)tall; // 富 - (BOOL)rich; // 帅 - (BOOL)handsome; @end
Person.m
// 1、10进制位赋值 //#define kTallMask 1 //#define kRichMask 2 //#define kHandsomeMask 4 // 2、2进制位赋值,0b:表示2进制位 //#define kTallMask 0b00000001 //#define kRichMask 0b00000010 //#define kHandsomeMask 0b00000100 // 3、运算符赋值 #define kTallMask (1<<0) #define kRichMask (1<<1) #define kHandsomeMask (1<<2) @interface Person(){ // char:长度一个字节(8个二进制位) char _tallRichHandsome;// 0b 0000 0000 } @end @implementation Person - (void)setTall:(BOOL)tall { if (tall) { // 掩码,位或(|)运算 _tallRichHandsome |= kTallMask; } else { //掩码取反,进行位与(&)运算 _tallRichHandsome &= ~kTallMask; } } - (void)setRich:(BOOL)rich { if (rich) { _tallRichHandsome |= kRichMask; } else { _tallRichHandsome &= ~kRichMask; } } - (void)setHandsome:(BOOL)handsome { if (handsome) { _tallRichHandsome |= kHandsomeMask; } else { _tallRichHandsome &= ~kHandsomeMask; } } - (BOOL)tall { // 掩码,位与(&)运算:两次取反(!!)返回Bool类型 return !!(_tallRichHandsome & kTallMask); } - (BOOL)rich { return !!(_tallRichHandsome & kRichMask); } - (BOOL)handsome { return !!(_tallRichHandsome & kHandsomeMask); } @end
main.m
#import <Foundation/Foundation.h> #import "Person.h" int main(int argc, const char * argv[]) { @autoreleasepool { Leader *person = [[Leader alloc] init]; person.tall = YES; person.rich = YES; person.handsome = YES; NSLog(@"-------tall:%hhd",person.tall); NSLog(@"-------rich:%hhd",person.rich); NSLog(@"-------handsome:%hhd",person.handsome); } return 0; }
2.2、结果分析
1、mask标记:表示掩码,一般用来位运算的。
2、掩码(mask)赋值有多种方式:
1、可以用10进制位赋值的。
2、用2进制位赋值的。
3、位移运算符赋值的,这里会用到左移运算符(<<)。
其中位移运算符赋值是最简单直观的,推荐使用这种方式。
3、掩码 + 位与(&)运算转Bool类型,需要两次取反(!!)。
三、结构体+位域实现
3.1、代码示例
#import <Foundation/Foundation.h> @interface Student : NSObject - (void)setTall:(BOOL)tall; - (void)setRich:(BOOL)rich; - (void)setHandsome:(BOOL)handsome; - (BOOL)tall; - (BOOL)rich; - (BOOL)handsome; @end
#import "Student.h" @interface Student(){ struct { char tall : 1; char rich : 1; char handsome : 1; }_tallRichHandsome; } @end @implementation Student - (instancetype)init { if (self = [super init]) { } return self; } - (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; }
3.2、分析
当取值的时候,如果用如下方式实现:
- (BOOL)handsome { return _tallRichHandsome.handsome; }
发现,取值时打印出来为-1,实际二进制值为(0b1111 1111),这不是我们想要的结果,怎么处理?
/** * tallRichHandsome.tall = 0b1 一个二进制位 * BOOL = 0b0000 0000 一个字节为8个二进制位 * ---------------------------------------------------------- * 结果 = 0b1111 1111 = 255(-1) * 分析:当一个二进制位赋值给一个8个二进制位时,前面不足的拿最左边值(1)的填补。 */
那么这个问题这么处理呢?
1、取值时,因为拿到的是bool类型,可以两个取反获取到想要的值(如上面👆的例子)。
第一次取反,只要不等于0,返回的都是false,二次取反,得到我们需要的值true。
2、根据位于临近填补的特点,设置占位为两位(如:0b01),得到的就是:0b0000 0001
四、结构体+位域优化
根据上面的问题处理,这里只要修改如下部分代码:
@interface Student(){ struct {// 结构体位域长度设置为2 char tall : 2; char rich : 2; char handsome : 2; }_tallRichHandsome; } @end // 取值不在需要二次取反 - (BOOL)tall { return _tallRichHandsome.tall; }
缺点:
结构体位域存储方式,变量在结构体中的位置,直接决定了在二进制中的存储位置。如代码有增删改操作,容易定位不准确,导致出错。
五、共用体(推荐)
这是OC内部底层实现的逻辑。
5.1、代码示例
#import <Foundation/Foundation.h> @interface Leader : NSObject - (void)setTall:(BOOL)tall; - (void)setRich:(BOOL)rich; - (void)setHandsome:(BOOL)handsome; - (BOOL)tall; - (BOOL)rich; - (BOOL)handsome; @end
#import "Leader.h" #define kTallMask (1<<0) #define kRichMask (1<<1) #define kHandsomeMask (1<<2) @interface Leader(){ union {// 共用体:大家都共用一个字节 char bits;//位 struct {// 结构体仅是提高代码可读性,没有实际应用 char tall : 1; char rich : 1; char handsome : 1; }; }_tallRichHandsome; } @end @implementation Leader - (void)setTall:(BOOL)tall { if (tall) { _tallRichHandsome.bits |= kTallMask; } else { _tallRichHandsome.bits &= ~kTallMask; } } - (void)setRich:(BOOL)rich { if (rich) { _tallRichHandsome.bits |= kRichMask; } else { _tallRichHandsome.bits &= ~kRichMask; } } - (void)setHandsome:(BOOL)handsome { if (handsome) { _tallRichHandsome.bits |= kHandsomeMask; } else { _tallRichHandsome.bits &= ~kHandsomeMask; } } - (BOOL)tall { return !!(_tallRichHandsome.bits & kTallMask); } - (BOOL)rich { return !!(_tallRichHandsome.bits & kRichMask); } - (BOOL)handsome { return !!(_tallRichHandsome.bits & kHandsomeMask); } @end
5.2、分析
这是位运算与结构体的位域结合方式,利用各自的优势。
那有什么优势呢?
1、位运算:定位精准,提高运算效率。
2、结构体的位域:提高代码的可读性。
六、OC中的应用
1、ISA_MASK
1、在arm64架构以前,isa指针就是一个普通的指针,存储着Class、Meta-Class对象的内存地址。
2、从arm64架构开始,对isa进行了优化,变成了一个共用体(union)结构,还使用位域存储了更多信息。需要 &ISA_MASK 才能得到Class、Meta-Class对象的内存地址。
# define ISA_MASK 0x0000000ffffffff8ULL
2、enum枚举
在OC中最常见的位运算是枚举设置
// 枚举设施孩子 typedef enum { ZMOptionsNone = 0, ZMOptionsOne = 1<<0,// 0b0001 ZMOptionsTwo = 1<<1,// 0b0010 ZMOptionsThree = 1<<2,// 0b0100 ZMOptionsFour = 1<<3,// 0b1000 }ZMOptions; // 取值 - (void)setOptions:(ZMOptions)options { if (options&ZMOptionsOne) { NSLog(@"ZMOptionsOne"); } if (options&ZMOptionsTwo) { NSLog(@"ZMOptionsTwo"); } if (options&ZMOptionsThree) { NSLog(@"ZMOptionsThree"); } if (options&ZMOptionsFour) { NSLog(@"ZMOptionsFour"); } } - (void)viewDidLoad { [super viewDidLoad]; // 赋值 [self setOptions:ZMOptionsOne|ZMOptionsFour]; }