前言
上文讨论了实例对象的内存模型,今天我们接着来分析,isa指针所指向内存模型。
一、回顾:实例对象的内存模型
在分析isa指针之前,我们先来回顾一下上文中的内容,假设,有一个这样的类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| @interface Main : NSObject
@property int value1; @property int value2; @property int value3;
+ (instancetype) sharedInstance;
- (void) run1; - (void) run2; - (void) run3; @end
@implementation Main
+(instancetype)sharedInstance { static Main * _instance = nil; static dispatch_once_t predicate; dispatch_once(&predicate, ^{ _instance = [[Main alloc] init]; }); return _instance; } - (void)run2 { NSLog(@"self = %p, run2 ",self); } - (void)run3 { NSLog(@"self = %p, run3 ",self); } - (void)run1 { NSLog(@"self = %p, run1 ",self); } @end
|
接下来,我们创建一个实例对象instance:
1 2 3 4
| Main * instance = [[Main alloc] init]; instance.value1 = 0x11111111; instance.value2 = 0x22222222; instance.value3 = 0x33333333;
|
instance对象占用内存/实际分配内存
instance对象实际本身占用20字节,内存对齐后会占用24字节,但实际上,alloc方法会为instance对象分配32字节。
我们可以通过以下代码验证:
1 2 3 4 5
| //实例对象内存对齐后实际占用大小 NSLog(@"%zu",class_getInstanceSize([instance class]));
//alloc方法实际为instance对象分配的内存大小 NSLog(@"%zu",malloc_size((__bridge const void *)(instance)));
|
instance内存模型

二、探究isa对象模型
要想知道isa的内存模型,首先需要知道isa的类型,isa类型信息如下:
1
| typedef struct objc_class *Class;
|
struct object_class的定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13
| struct objc_class : objc_object { // Class ISA; Class superclass; cache_t cache; // formerly cache pointer and vtable class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
class_rw_t *data() { return bits.data(); } void setData(class_rw_t *newData) { bits.setData(newData); } //......
|
我们可以看到,struct object_class继承于objc_object类型,而objc_object类型如下:
1 2 3 4 5 6 7 8 9 10
| struct objc_object { private: isa_t isa; public: Class ISA(); // getIsa() allows this to be a tagged pointer object Class getIsa(); //... }
|
如何获取isa指针?
而在objc_object的ISA方法里,我们可以得到获取isa指针的方法:
1 2 3 4 5 6
| inline Class objc_object::ISA() { assert(!isTaggedPointer()); return (Class)(isa.bits & ISA_MASK); }
|
ISA_MASK的定义如下:
1 2 3 4 5
| # if __arm64__ # define ISA_MASK 0x0000000ffffffff8ULL # elif __x86_64__ # define ISA_MASK 0x00007ffffffffff8ULL #endif
|
从上述ISA方法可知,instance对象前8字节并不是直接指向isa对象的指针,还需要&(按位与)一下ISA_MASK。
彩蛋:自己写一个getISA方法
实现原理很简单,拿到对象的前8字节,按位与上ISA_MASK即可拿到该对象的isa指针。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| # if __arm64__ # define ISA_MASK 0x0000000ffffffff8ULL # elif __x86_64__ # define ISA_MASK 0x00007ffffffffff8ULL #endif Class getIsa(id obj){ struct TMP_NSObject_IMPL { uintptr_t isa; }; struct TMP_NSObject_IMPL * pObj = (__bridge struct TMP_NSObject_IMPL*)obj; uintptr_t ptrIsa = ((uintptr_t)pObj->isa & ISA_MASK); struct TMP_NSObject_IMPL * isa = (void*)ptrIsa; return (__bridge Class)isa; }
|
isa对象本质
总结起来,struct object_class类型有4个成员变量:isa、superclass、cache和bits,这四个变量的类型都是指针类型,因此每个isa对象占用40字节。
1 2 3 4 5 6
| struct objc_class : objc_object { isa_t isa; //指向类对象或元类对象 Class superclass; //父类对象 cache_t cache; // formerly cache pointer and vtable class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags }
|
isa对象内存模型

从isa的内存模型来看,我们可以很轻易获取其父类、元类等,那么问题来了,类对象的方法、协议相关的东西会存在哪里呢?
没错,在bits所指向的内存里。
在struct objc_class中,有一个data方法,来获取bits成员变量所指向的内存:
1 2 3 4 5 6 7 8 9 10 11
| struct objc_class : objc_object { // Class ISA; Class superclass; cache_t cache; // formerly cache pointer and vtable class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
class_rw_t *data() { return bits.data(); } //.... }
|
class_rw_t的定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13
| struct class_rw_t { uint32_t flags; uint32_t version;
const class_ro_t *ro;
method_array_t methods; property_array_t properties; protocol_array_t protocols;
Class firstSubclass; Class nextSiblingClass; }
|
class_ro_t的定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| struct class_ro_t { uint32_t flags; uint32_t instanceStart; uint32_t instanceSize; #ifdef __LP64__ uint32_t reserved; #endif
const uint8_t * ivarLayout; const char * name; method_list_t * baseMethodList; protocol_list_t * baseProtocols; const ivar_list_t * ivars;
const uint8_t * weakIvarLayout; property_list_t *baseProperties;
method_list_t *baseMethods() const { return baseMethodList; } };
|
从class_ro_t的定义中,我们可以看到,该结构存储了类的名称、方法列表、协议、变量名等。
简略总结图
那么,整个instance实例的内存模型如下:
当instance需要
- 访问成员变量时,直接通过本身的地址+偏移即可。
- 想要调用实例方法时,通过
isa指针找到类对象,在类对象里找到该方法的地址,如果没有找到,则在其类对象的父类对象中再去寻找,直到找到NSObject的类对象,如果还没找到,返回unrecognized selector错误。
因此,如果想劫持某个实例对象的某个方法,就可以替换该对象的isa指针得以实现。