一般语言通过
编译链接之后,代码的执行效果已经确定。但是 OC 可以通过Runtime API动态修改程序执行效果,比如让[person run]运行时变成[cat eat]。Runtime API提供的接口基本是 C 语言的,源码由C/C++/汇编语言编写,源代码是开源的。
一、本节的主题是由浅入深,深入了解 isa
1. isa 的历史演变
- 在 arm64之前,isa是一个普通的指针,存储着Class、Meta-Class对象的内存地址
- 在 arm64之后,苹果对isa进行了优化,变成了一个共用体(union),还是用位域来存储更多的信息
2. 讲 isa 之前,我们来解决一些看似无关的问题 ,下面 person对象有三个数据需要存储tall、rich、handsome,思考 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;所以仅仅占用一个字节,就实现了分别存储tall、rich、handsome信息
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中的哪些数据代表tall、rich、handsome
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的- 元类对象和类对象
- 如果有读者明白其中原理,请留言告知,非常感谢。(找到问题了,因为需要用iOS真机测试,不能用mac模拟器) 
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 的类的属性中 
9. 有了上述知识,我们也可以很容易看懂 OC 中的枚举
typedef NS_OPTIONS(NSUInteger, UIViewAutoresizing) {
    UIViewAutoresizingNone                 = 0,
    UIViewAutoresizingFlexibleLeftMargin   = 1 << 0,
    UIViewAutoresizingFlexibleWidth        = 1 << 1,
    UIViewAutoresizingFlexibleRightMargin  = 1 << 2,
    UIViewAutoresizingFlexibleTopMargin    = 1 << 3,
    UIViewAutoresizingFlexibleHeight       = 1 << 4,
    UIViewAutoresizingFlexibleBottomMargin = 1 << 5
};
- 上述是我们经常见到的枚举,现在我们能够轻易读懂, 
- 并且知道为什么可以这样写 - UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleWidth
- 也可以知道,如果我们自己需要定义这样的枚举,内部如何获取每个枚举的值 
- 思考 - UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleWidth是否等于- UIViewAutoresizingFlexibleLeftMargin + UIViewAutoresizingFlexibleWidth