美文网首页iOS 开发 Objective-C
iOS 底层 day11 runtime isa详解

iOS 底层 day11 runtime isa详解

作者: 望穿秋水小作坊 | 来源:发表于2020-09-02 17:11 被阅读0次

一般语言通过编译链接之后,代码的执行效果已经确定。但是 OC 可以通过 Runtime API 动态修改程序执行效果,比如让 [person run] 运行时变成 [cat eat]Runtime API 提供的接口基本是 C 语言的,源码由 C/C++/汇编 语言编写,源代码是开源的。

一、本节的主题是由浅入深,深入了解 isa

1. isa 的历史演变
  • arm64 之前,isa 是一个普通的指针,存储着 ClassMeta-Class 对象的内存地址
  • arm64 之后,苹果对 isa 进行了优化,变成了一个共用体union),还是用位域来存储更多的信息
2. 将 isa 之前,我们来解决一些看似无关的问题 ,下面 person对象有三个数据需要存储tallrichhandsome,思考 person对象的三个数据在内存中实际占据多大?再思考如何让 person对象的三个数据 仅占用一个字节?
#import <Foundation/Foundation.h>
@interface Person : NSObjec
@property(nonatomic, assign) BOOL tall;
@property(nonatomic, assign) BOOL rich;
@property(nonatomic, assign) BOOL handsome;
@end
  • 三个 BOOL 类型的属性,所以一共占 3 个字节
  • 如何让 person对象的三个数据 仅占用一个字节?下面介绍三种方法,由浅入深
3. 让 person对象的三个数据 仅占用一个字节的方案一
#import "Person.h"
// 掩码
#define SPTallMask (1<<0)
#define SPRichMask (1<<1)
#define SPHandsomeMask (1<<2)

@implementation Person
{
    char _tallRichHandsome; // 仅占用一字节
}
// tall
- (void)setTall:(BOOL)tall{
    if (tall) {
        _tallRichHandsome = _tallRichHandsome | SPTallMask;
    } else {
        _tallRichHandsome = _tallRichHandsome & (~SPTallMask);
    }
}
- (BOOL)tall{
    return !!(_tallRichHandsome & SPTallMask);
}
// rich
- (void)setRich:(BOOL)rich{
    if (rich) {
        _tallRichHandsome = _tallRichHandsome | SPRichMask;
    } else {
        _tallRichHandsome = _tallRichHandsome & (~SPRichMask);
    }
}
- (BOOL)rich{
    return !!(_tallRichHandsome & SPRichMask);
}
// handsome
- (void)setHandsome:(BOOL)handsome{
    if (handsome) {
        _tallRichHandsome = _tallRichHandsome | SPHandsomeMask;
    } else {
        _tallRichHandsome = _tallRichHandsome & (~SPHandsomeMask);
    }
}
- (BOOL)handsome{
    return !!(_tallRichHandsome & SPHandsomeMask);
}

@end
  • 如上程序,数据部分,仅仅是 char _tallRichHandsome; 所以仅仅占用一个字节,就实现了分别存储 tallrichhandsome 信息
4. 让 person对象的三个数据 仅占用一个字节的方案二(优化版),使用结构体的位域实现
#import "Person.h"

@implementation Person
{
    struct {
        char tall : 1;
        char rich : 1;
        char handsome : 1;
    } _tallRichHandsome;
}
// tall
- (void)setTall:(BOOL)tall{
    _tallRichHandsome.tall = tall;
}
- (BOOL)tall{
    return !!_tallRichHandsome.tall;
}
// rich
- (void)setRich:(BOOL)rich{
    _tallRichHandsome.rich = rich;
}
- (BOOL)rich{
    return !!_tallRichHandsome.rich;
}
// handsome
- (void)setHandsome:(BOOL)handsome{
    _tallRichHandsome.handsome = handsome;
}
- (BOOL)handsome{
    return !!_tallRichHandsome.handsome;
}

@end
  • 上述代码结构体中,实际用到的是 3bit ,由于内存存储是以 byte 为单位,所以也占用 1byte
5. 让 person对象的三个数据 仅占用一个字节的方案 三(优化版),使用共用体+结构体的位域实现
#import "Person.h"
// 掩码
#define SPTallMask (1<<0)
#define SPRichMask (1<<1)
#define SPHandsomeMask (1<<2)

@implementation Person
{
    union {
        char bits;
        struct {
            char tall : 1;
            char rich : 1;
            char handsome : 1;
        };
    } _tallRichHandsome;
}
// tall
- (void)setTall:(BOOL)tall{
    if (tall) {
        _tallRichHandsome.bits = _tallRichHandsome.bits | SPTallMask;
    } else {
        _tallRichHandsome.bits = _tallRichHandsome.bits & (~SPTallMask);
    }
}
- (BOOL)tall{
    return !!(_tallRichHandsome.bits & SPTallMask);
}
// rich
- (void)setRich:(BOOL)rich{
    if (rich) {
        _tallRichHandsome.bits = _tallRichHandsome.bits | SPRichMask;
    } else {
        _tallRichHandsome.bits = _tallRichHandsome.bits & (~SPRichMask);
    }
}
- (BOOL)rich{
    return !!(_tallRichHandsome.bits & SPRichMask);
}
// handsome
- (void)setHandsome:(BOOL)handsome{
    if (handsome) {
        _tallRichHandsome.bits = _tallRichHandsome.bits | SPHandsomeMask;
    } else {
        _tallRichHandsome.bits = _tallRichHandsome.bits & (~SPHandsomeMask);
    }
}
- (BOOL)handsome{
    return !!(_tallRichHandsome.bits & SPHandsomeMask);
}

@end
  • 方案三,使用 联合体 + 结构体位域 我们也达到同样的效果
  • 这里的 struct 更多的是起了一个说明作用,说明 bits 中的哪些数据代表 tallrichhandsome
6. 接下来我们看一下 isa 指针的具体源码,是否一下子就理解其中含义了呢?
union isa_t 
{
    Class cls;
    uintptr_t bits;

# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL
#   define ISA_MAGIC_MASK  0x000003f000000001ULL
#   define ISA_MAGIC_VALUE 0x000001a000000001ULL
    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 deallocating      : 1;
        uintptr_t has_sidetable_rc  : 1;
        uintptr_t extra_rc          : 19;
#       define RC_ONE   (1ULL<<45)
#       define RC_HALF  (1ULL<<18)
    };
}
  • arm64 架构下,isa_t共用体占据 8 字节,也就是 64bit, struct 中的内容就是对 64bit 每位上代表的含义进行说明
  • 其中 shiftcls 就是我们真正的指针地址,这也解答了,为什么我们在获得 instance 对象的 isa 地址值之后,需要 isa地址值& ISA_MASK 才能获取真正的 Class 或者 Meta-Class 地址
  • uintptr_t shiftcls : 33; 前面有三个 bit 被占用,结合ISA_MASK 前三位为零,从而我们可以推敲出,无论是 Class 还是 Meta-Class 他们的地址值,前三位都是零,这个可以在项目中打印 Class 或 Meta-Class 的地址值可以得到验证。
7. 打印了 personClass(类对象) 和 personMetaClass(元类对象)
Demo[5657:409869] personClass : 0x100003278, personMetaClass: 0x100003250
Demo[5657:409869] objClass : 0x7fff99c5c118,objMetaClass : 0x7fff99c5c0f0
Demo[5657:409869] 常量区:0x1000032b0,堆区:0x100424b70,栈区:0x7ffeefbff55c
  • 上述代码,我们可以验证今天所学,所有 Class 或者 MetaClass,后三位 bit 上的值都为零,在十六进制下的表现就是 0 或 8

  • 然后我定义了三个确定存放在数据区堆区栈区 的变量地址

  • 我们观察可以发现 Person元类对象和类对象,都存放在常量区

  • 但是为什么 NSObject 的元类对象和类对象存放在 栈区之上的区域呢?

  • 目前只能猜测,栈区之上还有一部分给苹果官方用的保留区,存放的 NSObject元类对象和类对象

  • 如果有读者明白其中原理,请留言告知,非常感谢。

8. 部分 isa_t 共用体含义
  • nonpointer:

  • 0,代表普通的指针,存储在 Class、Meta-Class 对象的内存地址

  • 1,代表优化过,使用位域存储更多的信息

  • has_assoc

  • 是否有设置过关联对象,如果没有,释放时更快

  • has_cxx_dtor

  • 是否有 C++的析构函数(.cxx_destruct) ,如果没有,释放时更快

  • shiftcls

  • 存储着 Class、Meta-Class 对象的内存地址信息

  • magic

  • 用于调试时分辨对象是否未完成初始化

  • weakly_referenced

  • 是否有被弱引用指向过,如果没有,释放时会更快

  • deallocating

  • 对象是否正在释放

  • extra_rc

  • 里面存储的值是引用计数器减一

  • has_sidetable_rc

  • 引用计数器是否过大无法存储在 isa 中

  • 如果为 1,那么引用计算会存储在一个叫 SideTable 的类的属性中

相关文章

网友评论

    本文标题:iOS 底层 day11 runtime isa详解

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