联合体&位域

  1. 首先我们创建一个类,里面有三个BOOL类型属性
@interface SJPerson : NSObject

@property (nonatomic, assign) BOOL tall;
@property (nonatomic, assign) BOOL rich;
@property (nonatomic, assign) BOOL handsome;

@end
  1. 创建一个对象,打印内存大小
SJPerson *sj = [[SJPerson alloc] init];
sj.tall = NO;
sj.rich = YES;
sj.handsome = YES;
        
NSLog(@"%ld", class_getInstanceSize([sj class]));
image

打印出来占16字节(isa:8,tall:1,rich:1,handsome:1;16字节对齐)。
我们都知道BOOL类型,只有两种情况YESNO。那我们是不是可以这么设计,把每个属性都拿1个2进制位来存储,每个2进制位只有0和1两种情况,刚好和BOOL类型一样。这样三个属性只用3个2进制位就够了,那么这三个只占用一个字节,是不是大大减少了内存空间呢。
我们接下来实现一下这个方案。

  1. 我们就不能用属性了,属性自动就会占用一个字节。我们自己写getset方法。要占用一个字节,我们能想到占用一个字节的就是char类型,所以我们定义一个char类型的成员变量_tallRichHandsome。假设_tallRichHandsome数据是0b 0000 0000,用最右边一位代表tall,倒数第二位代表rich,倒数第三位代表handsome。每次获取值或者赋值的时候,我们只需要把对应位上的数据取出来即可。
    image
@interface SJPerson : NSObject

//@property (nonatomic, assign) BOOL tall;
//@property (nonatomic, assign) BOOL rich;
//@property (nonatomic, assign) BOOL handsome;

- (BOOL)tall;
- (BOOL)rich;
- (BOOL)handsome;

- (void)setTall:(BOOL)tall;
- (void)setRich:(BOOL)rich;
- (void)setHandsome:(BOOL)handsome;


@end
  1. 我们来复习下位运算
    按位与运算,同样位置都是1结果为1,否则为0
 0011
&1111
------
 0011

如果我想把0011的第三位取出来,是不是可以把它与0010做按位与运算,这样第三位是1,结果就是1,第三位是0,结果就是0,其他位都是0。取哪一位数据,就让那一位是1,其他位都是0。
按位或运算,只要这个位置有一个1,那么结果就是1,否则为0。

  0010 0011
| 0100 1000
-----------------
  0110 1011

取反操作

  0010 0110
~
-----------------
  1101 1001
  1. 我们取值就按位与操作,如果有值,证明就是YES,如果没有值,那就是NO。如果报错,我们可以加两个!强转一下。
- (BOOL)tall
{
    return _tallRichHandsome & 1;
}

- (BOOL)rich
{
    return _tallRichHandsome & 2;
}

- (BOOL)handsome
{
    return _tallRichHandsome & 4;
}

按位与运算的值,一般我们写成宏定义好一点。

// 掩码 一般做按位与运算
//#define SJTallMask 1
//#define SJRichMask 2
//#define SJHandsomeMask 4
// <<表示向左移几位,可清晰表示出第几个位置的数据
#define SJTallMask 1
#define SJRichMask (1 << 1)
#define SJHandsomeMask (1 << 2)

上面代码可以修改成

- (BOOL)tall
{
    return _tallRichHandsome & SJTallMask;
}

- (BOOL)rich
{
    return _tallRichHandsome & SJRichMask;
}

- (BOOL)handsome
{
    return _tallRichHandsome & SJHandsomeMask;
}
  1. 赋值按位或操作,如果要设置成1,只需要把原来的值和它的掩码按位或运算即可。如果要设置成0,需要做按位与操作,其他位都是1,要设置的位为0,这样我们设置的位即可设置成0。需要把掩码取反,然后再按位与操作即可。
- (void)setTall:(BOOL)tall
{
    if (tall) {
        _tallRichHandsome |= SJTallMask;
    } else {
        _tallRichHandsome &= ~SJTallMask;
    }
}

- (void)setRich:(BOOL)rich
{
    if (rich) {
        _tallRichHandsome |= SJRichMask;
    } else {
        _tallRichHandsome &= ~SJRichMask;
    }
}

- (void)setHandsome:(BOOL)handsome
{
    if (handsome) {
        _tallRichHandsome |= SJHandsomeMask;
    } else {
        _tallRichHandsome &= ~SJHandsomeMask;
    }
}

在iOS开发中,再看到xxxMask这种的,一般都是掩码用来做位运算的。
至此,我们就完成了用1个字节存储3个变量的操作。

  1. 虽然完成了,但是我们如果要加新变量,需要些set``get方法,还要写掩码,非常麻烦,我们进行优化。
    我们把_tallRichHandsome类型改成结构体,里面有三个char类型成员,这样结构体就占3个字节,怎么把它变成一个字节呢?结构体支持位域技术,在成员变量后面:加数字,数字就代表占几位。最先写的成员在二进制最低位。
@interface SJPerson ()
{
//    char _tallRichHandsome; /// 0b 0000 0000
    struct {
        char tall : 1;
        char rich : 1;
        char handsome : 1;
    }_tallRichHandsome;
}
@end

方法可以优化成下面代码。

- (BOOL)tall
{
    return _tallRichHandsome.tall;
}

- (BOOL)rich
{
    return _tallRichHandsome.rich;
}

- (BOOL)handsome
{
    return _tallRichHandsome.handsome;
}

- (void)setTall:(BOOL)tall
{
    _tallRichHandsome.tall = tall;
}

- (void)setRich:(BOOL)rich
{
    _tallRichHandsome.rich = rich;
}

- (void)setHandsome:(BOOL)handsome
{
    _tallRichHandsome.handsome = handsome;
}

这么写有个问题,比如tall设置成YES的时候,取值打印是-1,因为_tallRichHandsome.tall只占用一个字节,BOOL占8个字节,返回的时候,系统会把0b1填充成0b 1111 1111,结果就是-1了,我们取两次反就可以,因为-1也是YES

- (BOOL)tall
{
    return !!_tallRichHandsome.tall;
}

或者把结构体每个成员设置成占2位,0b 01会填充成0b 0000 0001,就没有问题了。

  1. 我们在研究下苹果官方做法。苹果官方也是用掩码做位运算来获取结果的。
    首先我们来了解下共用体,也叫联合体。简单来说就是共用体里面的成员变量公用同一片内存,共用体的内存大小是其成员里内存最大的那个值。
    我们这样做因为位运算取出来数据非常精准,不会像上面那样数据出问题;这样写增加代码可读性,_tallRichHandsome有三个成员,分别占用一个字节,这个结构体写不写都不影响代码执行结果,只是增加可读性。
@interface SJPerson ()

{
    union {
        char bits;
        struct {
            char tall : 1;
            char rich : 1;
            char handsome : 1;
        };
    }_tallRichHandsome;
}

@end

@implementation SJPerson

- (BOOL)tall
{
    return _tallRichHandsome.bits & SJTallMask;
}

- (BOOL)rich
{
    return _tallRichHandsome.bits & SJRichMask;
}

- (BOOL)handsome
{
    return _tallRichHandsome.bits & SJHandsomeMask;
}
- (void)setTall:(BOOL)tall
{
    if (tall) {
        _tallRichHandsome.bits |= SJTallMask;
    } else {
        _tallRichHandsome.bits &= ~SJTallMask;
    }
}

- (void)setRich:(BOOL)rich
{
    if (rich) {
        _tallRichHandsome.bits |= SJRichMask;
    } else {
        _tallRichHandsome.bits &= ~SJRichMask;
    }
}

- (void)setHandsome:(BOOL)handsome
{
    if (handsome) {
        _tallRichHandsome.bits |= SJHandsomeMask;
    } else {
        _tallRichHandsome.bits &= ~SJHandsomeMask;
    }
}

@end

总结:结构体每个成员占用自己的内存空间,结构体内存大小取决于所有成员内存之和;共用体所有成员占用同一片内存区域,每个成员是互斥的,共用体大小取决于成员中内存最大的那一个。

你可能感兴趣的:(联合体&位域)