| Date | Notes | Refer. |
|---|---|---|
| 2019-08-18 | 首次提交 | objc4-750.1 |

Preface
isa 指针是所有 Obj-C 对象中都拥有的一个成员。因为除了继承链之外,Obj-C 还特有一条从实例对象到类对象、元类对象的链。而后者正是依靠 isa 而串联起来的,那么本文就将结合源码谈谈 Obj-C 中的 isa。
对于不大熟悉 Obj-C 中对象的同学,可以先行阅读 Obj-C 中的对象一文。
Where
// NSObject.h
@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
Class isa OBJC_ISA_AVAILABILITY; // ⬅️
#pragma clang diagnostic pop
}
// objc-runtime-new.h
struct objc_class : objc_object {
// Class ISA;
}
// objc-private.h
struct objc_object {
private:
isa_t isa; // ⬅️
}
如上,对于实例对象,isa 定义在基类 NSObject 的内部被其它类所继承;对于类对象和元类对象,isa 在 objc_object 结构体中被继承。

实例对象中的 isa 指向类对象,类对象中的 isa 指向元类对象,元类对象中的 isa 指向根元类对象(包括根元类对象也指向自己)。
What
那么 isa 到底是什么呢?其实在 ARM 64 之前,isa 直接保存了类对象或者元类对象的地址,而之后使用了位域结构存储了更多信息。
我们可以在源码中追根溯源找到 isa_t 类型的共用体(关于共用体和位域,可参考 C/C++ 中的位域与共用体一文):
// objc-private.h
struct objc_object {
private:
isa_t isa;
// ...
}
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
};
#endif
};
// isa.h
// ARM 64
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
# define ISA_BITFIELD \
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)
// _uintptr_t.h
#ifndef _UINTPTR_T
#define _UINTPTR_T
typedef unsigned long uintptr_t;
#endif /* _UINTPTR_T */
为了最大程度地优化内存占用,isa 使用了位域与共用体结构,将 unsigned long 的 8 个字节分为 64 位来使用。在 ARM 64 结构之下,isa 中的内容如下:
| ISA_BITFIELD | Bits | Notes |
|---|---|---|
| nonpointer | 1 | 0:普通指针,直接存储类或元类对象的地址1:经过优化的指针,使用位域结构存储了更多信息 |
| has_assoc | 1 | 0:标记未设置过关联对象1:标记设置过关联对象 |
| has_cxx_dtor | 1 | 0:标记没有 C++ 析构函数1:标记有 C++ 析构函数 |
| shiftcls | 33 | 存储类或元类对象的地址 |
| magic | 6 | 调试时分辨对象是否未完成初始化 |
| weakly_referenced | 1 | 0:标记未弱引用过1:标记弱引用过 |
| deallocating | 1 | 0:对象未正在释放1:对象正在释放 |
| has_sidetable_rc | 1 | 0:引用计数存储在 extra_rc1:引用计数过大,存储在 side table 类的属性中 |
| extra_rc | 19 | 存储引用计数 - 1 |
得到了 isa 中各个成员的位数,这样我们就可以通过掩码来运算得出我们想要的部分。比如 ISA_MASK,0x0000000ffffffff8 换算为二进制即 0b 0000 0000 0000 0000 0000 0000 0000 1111 1111 1111 1111 1111 1111 1111 1111 1000,因此如果使用 & 逻辑与将把 shiftcls 即类或元类对象的地址得出:
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
@interface Foo : NSObject
@end
@implementation Foo
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
Foo *foo = [[Foo alloc] init];
NSLog(@"%p", object_getClass(foo));
}
}
// OUTPUT:
// 0x100002478
// LLDB:
// (lldb) p/x foo->isa
// (Class) $0 = 0x001d800100002479 Foo
// (lldb) p/x 0x001d800100002479 & 0x0000000ffffffff8ULL
// (unsigned long long) $1 = 0x0000000100002478