探究Objective-C对象的内存模型(二):isa对象模型
前言
上文讨论了实例对象的内存模型,今天我们接着来分析,isa指针所指向内存模型。
一、回顾:实例对象的内存模型
在分析isa指针之前,我们先来回顾一下上文中的内容,假设,有一个这样的类:
@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
:
Main * instance = [[Main alloc] init];
instance.value1 = 0x11111111;
instance.value2 = 0x22222222;
instance.value3 = 0x33333333;
instance
对象占用内存/实际分配内存
instance
对象实际本身占用20字节,内存对齐后会占用24字节,但实际上,alloc
方法会为instance
对象分配32字节。
我们可以通过以下代码验证:
//实例对象内存对齐后实际占用大小
NSLog(@"%zu",class_getInstanceSize([instance class]));
//alloc方法实际为instance对象分配的内存大小
NSLog(@"%zu",malloc_size((__bridge const void *)(instance)));
instance
内存模型
二、探究isa对象模型
要想知道isa
的内存模型,首先需要知道isa
的类型,isa
类型信息如下:
typedef struct objc_class *Class;
struct object_class
的定义如下:
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
类型如下:
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
指针的方法:
inline Class
objc_object::ISA()
{
assert(!isTaggedPointer());
return (Class)(isa.bits & ISA_MASK);
}
ISA_MASK
的定义如下:
# 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
指针。
# 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字节。
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
成员变量所指向的内存:
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
的定义如下:
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
的定义如下:
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
指针得以实现。