runtime OC

isa指针在64位系统后为什么要用位域方式去计算?

Posted on 2021-08-08,5 min read

在arm64架构之前,isa就是一个普通的指针,存储着Class、Meta-Class对象的内存地址;
从arm64架构开始,对isa进行了优化,变成了一个共用体 "union" 结构,还使用位域来存储更多的信息。
所以,为什么要使用这种方式来优化呢?


  • 首先我们看下64位系统下isa指针的结构
isa指针结构
  • 结论:使用共用体 union,可以在有限的空间内,同样是8个字节,我们可以存储更多的信息

我们以Person对象为例,来逐步了解 isa 使用 union 的优化方式;

1. 普通的Person对象

我们以Person这个对象为例:假设它有3个成员变量,tail、rich、handsome,那么正常情况下它需要占用16个字节;

@interface Person : NSObject
@property (nonatomic, assign) BOOL tall;
@property (nonatomic, assign) BOOL rich;
@property (nonatomic, assign) BOOL handsome;
@end

3个成员变量占用3个字节,isa指针占用8个字节,所以一共占用11个字节,内存对齐后,总共占用16个字节

NSLog(@"正常的 Person 对象占用:%zd 个字节",class_getInstanceSize([Person class]));
// 输出:正常的 Person 对象占用:16 个字节

2. 以位运算的方式优化

1个成员变量_tallRichHandsome 占用1个字节,isa指针8个字节,内存对其后,总共占用16个字节;

  • 我们不声明其成员变量,直接创建对应的方法;

    @interface Person : NSObject
    - (void)setTall:(BOOL)tall;
    - (void)setRich:(BOOL)rich;
    - (void)setHandsom:(BOOL)handsome;
    
    - (BOOL)isTall;
    - (BOOL)isRich;
    - (BOOL)isHandsome;
    @end
    
  • 声明一个char类型的对象,用来按位存储tall、rich、handsome

    @interface Person(){
        char _tallRichHandsome; // 0b 0000 0111 ; 占1个字节
    }
    @end
    
  • 通过位运算,实现set、get方法

    #define TallMask (1<<0) // 0000 0001 (掩码)
    #define RichMask (1<<1) // 0000 0010
    #define HandsomeMask (1<<2) // 0000 0100
    
    @interface Person(){
        char _tallRichHandsome; // 0b 0000 0111 ; 占1个字节
    }
    @end
    
    @implementation Person
    - (void)setTall:(BOOL)tall{
        if (tall) {
            _tallRichHandsome |= TallMask; // 按位或
        } else {
            _tallRichHandsome &= ~TallMask; // 取反后按位与
        }
    }
    - (void)setRich:(BOOL)rich{
        // 同setTall方法...
    }
    - (void)setHandsome:(BOOL)handsome{
        // 同setTall方法...
    }
    
    // 跟掩码进行与运算,再取2次反后,结果即为期待值(只要大于0,则为YES)
    - (BOOL)isTall{
        return !!(_tallRichHandsome & TallMask);
    }
    - (BOOL)isRich{
        return !!(_tallRichHandsome & RichMask);
    }
    - (BOOL)isHandsome{
        return !!(_tallRichHandsome & HandsomeMask);
    }
    @end
    

3. 利用结构体继续优化 (位域)

  • 利用结构体 代替 char类型的 _tallRichHandsome,然后利用位域,保证_tallRichHandsome仅占1个字节(不然的话,每个char都占1个字节,一共又占用3个字节了);

    @interface Person(){
        struct {
            char tall : 1; // :1 位域,代表只占1位
            char rich : 1;
            char handsome : 1;
        } _tallRichHandsome; // 按照先后顺序从右向左排 0b 0000 0000
    }
    @end
    
    @implementation Person
    - (void)setTall:(BOOL)tall{
        _tallRichHandsome.tall = tall;
    }
    - (void)setRich:(BOOL)rich{
        _tallRichHandsome.rich = rich;
    }
    - (void)setHandsome:(BOOL)handsome{
        _tallRichHandsome.handsome = handsome;
    }
    
    - (BOOL)isTall{
        return _tallRichHandsome.tall;
    }
    - (BOOL)isRich{
        return _tallRichHandsome.rich;
    }
    - (BOOL)isHandsome{
        return _tallRichHandsome.handsome;
    }
    @end
    
  • 此时输出,会发现BOOL输出了-1,打断点发现其为255;
    原因是 tall 只占1个二进制位,即为0b1,return tall 的时候,强制转为 8位 的 BOOL 类型,程序会默认按照自己的规则转,结果转成了 0b 1111 1111 (255),最高位为1,所以为 -1 的补码,所以输出了 -1 (减1后得反码,再取反后得原码)

    Person *person = [[Person alloc] init];
    person.tall = YES;
    person.rich = YES;
    person.handsome = NO;
    
    NSLog(@"tall: %d ; rich: %d ; handsome: %d",person.isTall,person.isRich,person.isHandsome); 
    // tall: -1 ; rich: -1 ; handsome: 0
    
    BOOL isTall = person.isTall; // 255
    

    解决方式为:

    - (BOOL)isTall{
        return !!_tallRichHandsome.tall; // 取值的时候,2次取反即可
    } 
    

4. 用 union 共用体 优化

因为共用体内共用同一块内存,所以 bits 和 struct 结构体 共用 他们最大的内存:1个字节;实际上和 "2. 以位运算的方式优化" 是一样的

@interface Person(){
    union { // 共用体
        char bits;
        struct { // 增加可读性,无任何作用,删除也不影响结果
            char tall : 1;
            char rich : 1;
            char handsome : 1;
        };
    } _tallRichHandsome;
}
@end

@implementation Person
- (void)setTall:(BOOL)tall{
    if (tall) {
        _tallRichHandsome.bits |= TallMask;
    } else {
        _tallRichHandsome.bits &= ~TallMask;
    }
}
- (void)setRich:(BOOL)rich{
    // 同setTall方法...
}
- (void)setHandsome:(BOOL)handsome{
    // 同setTall方法...
}
- (BOOL)isTall{
    return !!(_tallRichHandsome.bits & TallMask);
}
- (BOOL)isRich{
    return !!(_tallRichHandsome.bits & RichMask);
}
- (BOOL)isHandsome{
    return !!(_tallRichHandsome.bits & HandsomeMask);
}
@end

下一篇: 终端-效率工具→

loading...