前言

上文讨论了实例对象的内存模型,今天我们接着来分析,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_objectISA方法里,我们可以得到获取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个成员变量:isasuperclasscachebits,这四个变量的类型都是指针类型,因此每个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指针得以实现。