OC底层->isa 详解、位运算、枚举拓展。

585 阅读4分钟
  • 要想学习Runtime,首先要了解它底层的一些常用数据结构,比如isa指针
  • 在arm64架构之前,isa就是一个普通的指针,存储着Class、Meta-Class对象的内存地址
  • 从arm64架构开始,对isa进行了优化,变成了一个共用体(union)结构,还使用位域来存储更多的信息 image.png
  • 联合体(共同体)union是什么呢?

源码查看

  • 找到这个isa 这个不是 class 而是一个isa_t image.png
  • 这个isa_t是一个union image.png
  • 具体定义在isa.h image.png

image.png

  • 这些应该就是架构了-不同的架构他的掩码也是不一样的位运算也是不一样的。 image.png

isa详解-位域

  • nonpointer
    • 0,代表普通的指针,存储着Class、Meta-Class对象的内存地址
    • 1,代表优化过,使用位域存储更多的信息
  • has_assoc
    • 是否有设置过关联对象,如果没有,释放时会更快
  • has_cxx_dtor
    • 是否有C++的析构函数(.cxx_destruct),如果没有,释放时会更快
  • shiftcls
    • 存储着Class、Meta-Class对象的内存地址信息
  • magic
    • 用于在调试时分辨对象是否未完成初始化
  • weakly_referenced
    • 是否有被弱引用指向过,如果没有,释放时会更快
  • deallocating
    • 对象是否正在释放
  • extra_rc
    • 里面存储的值是引用计数器减1
  • has_sidetable_rc
    • 引用计数器是否过大无法存储在isa中
    • 如果为1,那么引用计数会存储在一个叫SideTable的类的属性中

为什么这么用呢

  • 举例 定义了一个类这个类有三个bool属性 image.png

  • 类的赋值与取值 image.png

  • 大小为 16个字节 一个bool一个字节 + isa 8个字节 = 11字节 -> 内存对齐后 16个字节 NSLog(@"%zd", class_getInstanceSize([MJPerson class]));

  • 但是 bool 只有0和1 0000 0001 或 0000 0000 实际上用到的只要一个二进制位

  • 所以设想能不能把这三个bool放到一个字节里去 bool a , bool b , bool c

    • 0000 0100 表示 a=YES b=NO c=NO
    • 0000 0010 表示 a=NO b=YES c=NO
    • 0000 0001 表示 a=NO b=NO c=YES
  • 代码

#import <Foundation/Foundation.h>

@interface MJPerson : NSObject
//@property (assign, nonatomic, getter=isTall) BOOL tall;
//@property (assign, nonatomic, getter=isRich) BOOL rich;
//@property (assign, nonatomic, getter=isHansome) BOOL handsome;

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

- (BOOL)isTall;
- (BOOL)isRich;
- (BOOL)isHandsome;


@end

#import "MJPerson.h"

// &可以用来取出特定的位

// 0000 0111
//&0000 0100
//------
// 0000 0100

// 掩码,一般用来按位与(&)运算的
//#define MJTallMask 1
//#define MJRichMask 2
//#define MJHandsomeMask 4

//#define MJTallMask 0b00000001
//#define MJRichMask 0b00000010
//#define MJHandsomeMask 0b00000100

#define MJTallMask (1<<0)
#define MJRichMask (1<<1)
#define MJHandsomeMask (1<<2)

@interface MJPerson()
{
    char _tallRichHansome;
}
@end

@implementation MJPerson


// 0010 1010
//&1111 1101
//----------
// 0010 1000

- (instancetype)init
{
    if (self = [super init]) {
        _tallRichHansome = 0b00000100;
    }
    return self;
}

- (void)setTall:(BOOL)tall
{
    if (tall) {
        _tallRichHansome |= MJTallMask;
    } else {
        _tallRichHansome &= ~MJTallMask;
    }
}

- (BOOL)isTall
{
    return !!(_tallRichHansome & MJTallMask);
}

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

- (BOOL)isRich
{
    return !!(_tallRichHansome & MJRichMask);
}

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

- (BOOL)isHandsome
{
    return !!(_tallRichHansome & MJHandsomeMask);
}

@end

  • 自己写set和get
  • _tallRichHansome 定义这个用来保存三个变量的值 0b0000 0000 一个字节

取值

  • 假设 tall 在 最后 0000 0001
  • rich 在 0000 0010
  • hansome 在 0000 0100

与 &运算

  • 如果两个值都为1结果就为1否则为0
    // 0011
    //&1111
    //-------
    // 0011
  • 假设我们现在要取出 rich这个值就是倒数第二位的这个1就与上0010(想去出的位为1,其他位为0)结果就是我们们要的位的值就是1或0,其他位的值必定为0。
    // 0011
    //&0010
    //-------
    // 0010
  • 所以
    • 取出tall值就与 0001 -> 2进制->10进制 (= 1) -> (1<<0) 1 左移0 位
    • 取出rich值就与 0010 -> 2进制->10进制 (= 2) -> (1<<1) 1 左移1 位
    • 取出hansome值就与 0100 -> 2进制->10进制 (= 4) -> (1<<2) 1 左移2 位
    • 这里定义的宏定义就是这个意思了 image.png
  • 假设 这个存放的值为 0000 0100 image.png
  • 外部调用了get方法取出这个1 (_tallRichHansome & MJHandsomeMask) image.png
    // 0000 0100
    //&0000 0100
    //-----------
    // 0000 0100
  • 计算结果为4结果不为0。
    • 不为0 : 4!= 0 -> 0! = 1 -> YES 4取反后为0,0取反后为1,1转bool为yes
    • 为0 双取反后为0 转bool 为 NO
  • 其他两个同理

设值

person.handsome = YES;
person.rich = YES;
person.tall = NO;
  • 外部调用set方法设值2个值为YES,一个为NO 里面的 _tallRichHansome 应该为 0000 0110;

| 或运算

  • 或:只要其中一个为1就为1 // 0000 0000
    //|0000 0110
    //----------
    // 0000 0110
  • 查看上面的计算就知道了设值为YES的时候用或
    • handsome 设值为YES的时候就或上一个4
    • rich 设值为YES的时候就或上一个2
    • tall 设值为YES的时候就或上一个1 image.png

设值为NO的时候

// 0000 0010
//&1111 1101
//----------
// 0000 0000

  • 设值为NO的时候还是用&
    • handsome 设值为NO的时候就与 1111 1011
    • rich 设值为NO的时候就与 1111 1101
    • tall 设值NO的时候就与 1111 1110
  • 掩码取反后与运算 “~”按位取反 image.png image.png

优化

  • 源码
#import <Foundation/Foundation.h>

@interface MJPerson : NSObject
//@property (assign, nonatomic, getter=isTall) BOOL tall;
//@property (assign, nonatomic, getter=isRich) BOOL rich;
//@property (assign, nonatomic, getter=isHansome) BOOL handsome;

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

- (BOOL)isTall;
- (BOOL)isRich;
- (BOOL)isHandsome;
- (BOOL)isThin;

@end

#import "MJPerson.h"

#define MJTallMask (1<<0)
#define MJRichMask (1<<1)
#define MJHandsomeMask (1<<2)
#define MJThinMask (1<<3)

@interface MJPerson()
{
    union {
        int bits;
        
        struct {
            char tall : 4;
            char rich : 4;
            char handsome : 4;
            char thin : 4;
        };
    } _tallRichHandsome;
}
@end

@implementation MJPerson

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

- (BOOL)isTall
{
    return !!(_tallRichHandsome.bits & MJTallMask);
}

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

- (BOOL)isRich
{
    return !!(_tallRichHandsome.bits & MJRichMask);
}

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

- (BOOL)isHandsome
{
    return !!(_tallRichHandsome.bits & MJHandsomeMask);
}



- (void)setThin:(BOOL)thin
{
    if (thin) {
        _tallRichHandsome.bits |= MJThinMask;
    } else {
        _tallRichHandsome.bits &= ~MJThinMask;
    }
}

- (BOOL)isThin
{
    return !!(_tallRichHandsome.bits & MJThinMask);
}

@end

  • char tall : 1;
    • :1 表示只占用1位
  • 这个结构体只是为了代码可读性并没有实际用处,真正作用的是int bits;int 占四个字节 0x0000 0000 image.png

image.png

  • 得出结论isa指针真正有用的就是这个bits image.png

  • 所以我们知道了这个isa指针的8个字节中可以通过掩码找到很多的信息

  • 比如这个掩码 image.png

  • bits与上这个掩码 就是能找到这些位上的数据了是1还是0呢就知道了

  • 得知后三位为零所以isa&上掩码-> 类对象和元类对象的地址后三位一定为0 image.png

  • 类或元类的地址是后1位就只能是8或0 image.png

位运算后的枚举拓展

typedef enum {
//    MJOptionsNone = 0,    // 0b0000
    MJOptionsOne = 1<<0,   // 0b0001
    MJOptionsTwo = 1<<1,   // 0b0010
    MJOptionsThree = 1<<2, // 0b0100
    MJOptionsFour = 1<<3   // 0b1000
} MJOptions;

- (void)setOptions:(MJOptions)options
{
    if (options & MJOptionsOne) {
        NSLog(@"包含了MJOptionsOne");
    }
    
    if (options & MJOptionsTwo) {
        NSLog(@"包含了MJOptionsTwo");
    }
    
    if (options & MJOptionsThree) {
        NSLog(@"包含了MJOptionsThree");
    }
    
    if (options & MJOptionsFour) {
        NSLog(@"包含了MJOptionsFour");
    }
}

//    [self setOptions: MJOptionsOne | MJOptionsFour];
//    [self setOptions: MJOptionsOne + MJOptionsTwo + MJOptionsFour];

//    self.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleTopMargin;
//
//    NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
//
//    [self addObserver:self forKeyPath:@"age" options:options context:NULL];