类的底层原理探索(上)

236 阅读5分钟

isa指针指向分析

类对象

我们在objc源码中查找Class的实现代码如下(因内容太多省略大部分代码)。

typedef struct objc_class *Class;

struct objc_class : objc_object {
    objc_class(const objc_class&) = delete;
    objc_class(objc_class&&) = delete;
    void operator=(const objc_class&) = delete;
    void operator=(objc_class&&) = delete;
    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() const {
        return bits.data();
    }

从源码中可以看出,Class是一个叫做objc_class的结构体,而这个结构体又继承自objc_object结构体,在对对象的探索过程中发现了对象实际上就是objc_object结构体,所以类的本质也是对象,也是objc_object结构体。

内存中有几个类对象

我们对同一个类的使用多个对象来获取类对象,然后查看获取到的类对象,看看有什么发现。

void lgTestClassNum(void){
    Class class1 = [LGPerson class];
    Class class2 = [LGPerson alloc].class;
    Class class3 = object_getClass([LGPerson alloc]);
    NSLog(@"\n%p-\n%p-\n%p",class1,class2,class3);
    // 打印结果为:0x100008368-0x100008368-0x100008368
}

通过打印结果发现,就算创建了多个对象,所有对象的类对象指向了同一个内存地址,所以对象的类对象在内存中仅存在一个。

类对象的isa指针

由于类对象是继承自objc_object,那它就继承了isa指针。对象的isa指针指向类对象,那类对象的isa又指向何方呢?

我们通过运行代码, 打断点,查看内存地址来一步步探索其中的奥秘。首先我们先查看对象p的4个8字节的内存地址。

image.png

接下来探索过程描述和图片结合如下。

WechatIMG615.png

探索到这里,isa指针的指向已经到了头。虽然对一些isa指针所指向的对象我们并不清楚,但基本的isa指向方法我们有个大概的了解了。

WechatIMG616.png

接下来,我们通过runtime来帮助我们认识这些未知的东西。

WechatIMG620.png 最终我们将那么未知的对象找了出来,有了如下结论。

WechatIMG621.png

完整的isa指针指向图如下 image.png

元类的继承链

定义LGPerson类继承自NSObject,定义LGTeacher类继承自LGPerson

@interface LGPerson : NSObject
@end
@interface LGTeacher : LGPerson
@end

接下来,通过runtime来探索元类的继承关系。

void testMetaSuper(void){
    // NSObject实例对象
    NSObject *object1 = [NSObject alloc];
    // NSObject类对象
    Class class = object_getClass(object1);
    // NSObject元类(根元类),获取类对象的类等同于获取元类,即objc_getMetaClass("NSObject");
    Class metaClass = object_getClass(class);
    
    // 打印为:NSObject实例对象:0x105326780
    NSLog(@"NSObject实例对象:%p",object1);
    // 打印为:NSObject类对象:0x7ff84a759030
    NSLog(@"NSObject类对象:%p",class);
    // 打印为:NSObject元类(根元类):0x7ff84a758fe0
    NSLog(@"NSObject元类(根元类):%p",metaClass);
    
    // LGPerson  -- 元类的父类就是父类的元类
    Class pMetaClass = objc_getMetaClass("LGPerson");
    Class psuperClass = class_getSuperclass(pMetaClass);
    
    // 打印为:LGPerson - 0x100008340
    NSLog(@"%@ - %p",pMetaClass,pMetaClass);
    // 打印为:NSObject - 0x7ff84a758fe0,LGPerson元类的父类是根元类
    NSLog(@"%@ - %p",psuperClass,psuperClass);
    
    // LGTeacher元类的父类 就是 LGPerson(LGPerson的元类)
    Class tMetaClass = objc_getMetaClass("LGTeacher");
    Class tsuperClass = class_getSuperclass(tMetaClass);
    
    // 打印为:LGPerson - 0x100008340,LGTeacher元类的父类是LGPerson的元类
    NSLog(@"%@ - %p",tsuperClass,tsuperClass);
    
    // NSObject的父类
    Class nsuperClass = class_getSuperclass(NSObject.class);
    
    // 打印为:(null) - 0x0,NSObject类对象没有父类
    NSLog(@"%@ - %p",nsuperClass,nsuperClass);
    
    // 根元类的父类 -- NSObject
    Class rnsuperClass = class_getSuperclass(metaClass);
    // 打印为:NSObject - 0x7ff84a759030,根元类的父类是NSObject类对象
    NSLog(@"%@ - %p",rnsuperClass,rnsuperClass);

}

由上面的代码,我们可以得出元类的继承关系图。

image.png

内存平移

// 数组指针
int c[4] = {5,6,7,9};
int *d= c;
NSLog(@"%p - %p - %p - %p - %p",&c,&c[0],&c[1],&c[2],&c[3]);
NSLog(@"%p - %p - %p - %p",d,d+1,d+2,d+3);

for (int i = 0; i<4; i++) {
    int value =  *(d+i);
    NSLog(@"%d",value);
}

// 打印如下
// 0x7ff7bfeff2c0 - 0x7ff7bfeff2c0 - 0x7ff7bfeff2c4 - 0x7ff7bfeff2c8 - 0x7ff7bfeff2cc
// 0x7ff7bfeff2c0 - 0x7ff7bfeff2c4 - 0x7ff7bfeff2c8 - 0x7ff7bfeff2cc
// 5
// 6
// 7
// 9

d是数组c的首地址指针,当对d进行+操作时,就是对c数组以数组类型为大小进行内存平移,即d+1=c[1],以此类推。

实例方法、属性在类中的存储位置

实例方法

查看源码分析

虽然我们查看了objc_class源码的实现,但是并没有在其中发现类的方法或属性之类的信息,于是我们继续查看一些成员变量的实现。

objc_class中有一个class_data_bits_t类型的成员,那class_data_bits_t又是什么呢?我们接着查看它的源码。

struct class_data_bits_t {
    friend objc_class;

    // Values are the FAST_ flags above.
    uintptr_t bits;
    // 省略了一些非关键代码
public:

    class_rw_t* data() const {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }
    // 省略了一些非关键代码
}

class_data_bits_t中有一个指针bits,另外还有一个data函数,返回class_rw_t类型的指针,查看class_rw_t源码(省略了部分代码)。

struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint16_t witness;
#if SUPPORT_INDEXED_ISA
    uint16_t index;
#endif

    explicit_atomic<uintptr_t> ro_or_rw_ext;

    Class firstSubclass;
    Class nextSiblingClass;

private:
    using ro_or_rw_ext_t = objc::PointerUnion<const class_ro_t, class_rw_ext_t, PTRAUTH_STR("class_ro_t"), PTRAUTH_STR("class_rw_ext_t")>;

    const ro_or_rw_ext_t get_ro_or_rwe() const {
        return ro_or_rw_ext_t{ro_or_rw_ext};
    }
    
    class_rw_ext_t *ext() const {
        return get_ro_or_rwe().dyn_cast<class_rw_ext_t *>(&ro_or_rw_ext);
    }

    const class_ro_t *ro() const {
        auto v = get_ro_or_rwe();
        if (slowpath(v.is<class_rw_ext_t *>())) {
            return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->ro;
        }
        return v.get<const class_ro_t *>(&ro_or_rw_ext);
    }

    const method_array_t methods() const {
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->methods;
        } else {
            return method_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseMethods};
        }
    }

    const property_array_t properties() const {
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->properties;
        } else {
            return property_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProperties};
        }
    }

    const protocol_array_t protocols() const {
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->protocols;
        } else {
            return protocol_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProtocols};
        }
    }
};

我们惊奇地发现,在class_rw_t中存在着命名为methodspropertiesprotocols这些返回了数组的函数,是否这些函数返回的就是类所拥有的方法、属性和遵从的协议呢?

验证

既然我们已经通过源码找到了探索的方向,接下来我们通过代码和控制台来帮我们验证一下。

先定义一个类用于我们做验证。

@interface LGPerson : NSObject {
    NSString *_hobby;
}

@property (nonatomic ,copy) NSString *name;
@property (nonatomic ,assign) int age;

- (void)instanceMethod;
+ (void)classMethod;

@end

首先我们需要获取到class_data_bits_t。但我们应该怎样得到class_data_bits_t呢?

我们继续查看最开始我们贴出的objc_class的实现,由于其继承了objc_objct,所以它也应该有一个8字节的isa指针,Class superclass是8字节的指针,cache_t cache占16字节(后续再具体探索cache_t,当前只要知道它占16字节就行),class_data_bits_t bits占多少字节暂不得而知。

那我们得出结论,class_data_bits_t是在类的首地址偏移32字节的位置。知道了位置以后,我们就可以通过类对象的首地址来进行偏移得到class_data_bits_t。下图中类对象的首地址为0x100008250,偏移32位即0x100008270。由于我们已经知道了这段内存地址存储的是什么东西,那我们就可以直接对其进行强转。

image.png 当我们已经有了class_data_bits_t的指针以后,就可以对其进行一系列的操作,一步步验证我们刚才根据源码获取到的信息。

WechatIMG622.png 我们查看method_list_t的实现,发现它是entsize_list_tt模板类的派生类,那我们接着查看entsize_list_tt

struct method_list_t : entsize_list_tt<method_t, method_list_t, 0xffff0003, method_t::pointer_modifier> {
}

entsize_list_tt是一个容器模板,并且我们在它里面发现了一个可以获取其元素的Element& get(uint32_t i)函数。

template <typename Element, typename List, uint32_t FlagMask, typename PointerModifier = PointerModifierNop>
struct entsize_list_tt {
    uint32_t entsizeAndFlags;
    uint32_t count;

    uint32_t entsize() const {
        return entsizeAndFlags & ~FlagMask;
    }
    uint32_t flags() const {
        return entsizeAndFlags & FlagMask;
    }

    Element& getOrEnd(uint32_t i) const { 
        ASSERT(i <= count);
        return *PointerModifier::modify(*this, (Element *)((uint8_t *)this + sizeof(*this) + i*entsize()));
    }
    Element& get(uint32_t i) const { 
        ASSERT(i < count);
        return getOrEnd(i);
    }
    // 省略后面的代码
}

我们尝试通过get函数来获取刚才已经得到的method list。

image.png 经过尝试,我们并没有获取到有用的信息,于是我们继续查看method_t的源码。

struct method_t {
    static const uint32_t smallMethodListFlag = 0x80000000;
    
    struct big {
        SEL name;
        const char *types;
        MethodListIMP imp;
    };
    struct small {
        RelativePointer<const void *> name;
        RelativePointer<const char *> types;
        RelativePointer<IMP, /*isNullable*/false> imp;

        bool inSharedCache() const {
            return (CONFIG_SHARED_CACHE_RELATIVE_DIRECT_SELECTORS &&
                    objc::inSharedCache((uintptr_t)this));
        }
    };

    objc_method_description *getDescription() const {
        return isSmall() ? getSmallDescription() : (struct objc_method_description *)this;
    }
};

struct objc_method_description {
    SEL _Nullable name;               /**< The name of the method */
    char * _Nullable types;           /**< The types of the method arguments */
};

我们发现在method_t中有bigsmall两个结构体,其中都有包含nametype等信息,并且有一个getDescription函数返回一个objc_method_description指针,而objc_method_description结构体里,也定义了SEL namechar * types,那我们就尝试通过getDescription来获取我们想要的信息。

image.png 果不其然,找到了我们想要的东西,我们定义的instanceMethod方法。那接下来把list中所有的都取出来看一下。

image.png methods中包括两个属性的get和set方法,还有我们定义的实例方法instanceMethod,还有析构方法,但是并没有发现我们定义的类方法,那类方法又是存放在哪里呢?先暂时放下这个疑问,我们用同样的方法查看一下属性。

属性

class_rw_t中,properties返回的是property_array_t,这个跟methods一样,其中存放的是property_t结构体。

class property_array_t : 
    public list_array_tt<property_t, property_list_t, RawPtr>
{
    typedef list_array_tt<property_t, property_list_t, RawPtr> Super;

 public:
    property_array_t() : Super() { }
    property_array_t(property_list_t *l) : Super(l) { }
};

struct property_t {
    const char *name;
    const char *attributes;
};

已经知道了properties的结构,那我们用同样的方法来获取properties

image.png 同样的,我们只看到了nameage属性,成员变量hobby并没有在properties中。

类方法和成员变量究竟存储在哪里呢?下一篇接着探索。