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