美文网首页
iOS-浅谈OC中的isa

iOS-浅谈OC中的isa

作者: 晴天ccc | 来源:发表于2019-05-14 09:19 被阅读0次

目录

  • 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的三个属性tallrichhandsome就可以保存在一个字节的数据当中:

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指针地址。

相关文章

  • iOS-浅谈OC中的isa

    目录 isa结构----nonpointer----has_assoc----has_cxx_dtor----sh...

  • iOS-浅谈OC中isa和superclass的指针指向

    目录 isa指针----isa指针指向superclass指针----class对象的superclass指针--...

  • iOS-浅谈OC中的Block

    KVO简介和基本使用 KVO全称是Key-Value Observing,俗称“键值监听”,可用于监听某个对象属性...

  • iOS-浅谈OC中的Runloop

    LLDB是Xcode自带的调试器,可以通过一些命令获得想要的信息 常用打印 读取内存 修改内存中的值 格式 字节大小

  • iOS-浅谈OC中的KVO

    OC对象的类型 Objective-C中的对象,简称OC对象,主要可以分为3种 instance对象(实例对象)c...

  • iOS-浅谈OC中的KVC

    【一】isa指针 思考消息发送过程 了解Runtime的可能知道,调用方法本质上是消息发送: 思考:实例对象方法保...

  • iOS-浅谈OC中的指针

    将OC代码转换为C/C++代码 打开终端,进入main.m文件所在文件夹下执行以下命令: 但是因为不同平台运行,O...

  • iOS-浅谈OC中的Category

    目录 简介基本使用问题思考最终结论Category的底层结构拓展-Category的底层结构Cagegory的加载...

  • OC底层探索07-isa的走位理解

    上一篇中对isa做了介绍OC底层探索06-isa本身藏了多少信息你知道吗?,下面就来看看isa在oc中的作用是什么...

  • isa-swizzling应用

    0x00 在OC中, isa是一个很重要的结构体(参考文章), OC runtime正是通过isa来确定obje...

网友评论

      本文标题:iOS-浅谈OC中的isa

      本文链接:https://www.haomeiwen.com/subject/hhejaqtx.html