OC底层原理-objc 818(三)bits

309 阅读8分钟

前言

上一节我们已经了解了objc_class结构体的内容了,也分析了isa和superclass的功能,我们在来看看objc_class结构体,来开启我们bits的探索之路。

struct objc_class : objc_object {
    // Class ISA;
    // Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;  
}

找到bits的地址

首先我们可以通过p/x打印出对象的首地址,我们知道每个指针占8字节,那么isasuperclass都是指针各占8字节,那么我们只要知道cache占多少字节,通过地址平移就可以知道bits的首地址了。

探索cache的内存大小

通过源码我们可以看到cache是一个cache_t的结构体

我们先看一下cache_t的结构体源码

struct cache_t {
private:
    explicit_atomic<uintptr_t> _bucketsAndMaybeMask;	// uintptr_t = long 占8字节
    union {										
        // 共用体,共用体的内存大等于成员中最大的成员占内存,所以占8字节
        struct {
            explicit_atomic<mask_t>    _maybeMask;		// mask_t = int32 占4字节
#if __LP64__
            uint16_t                   _flags;			// 占2字节
#endif
            uint16_t                   _occupied;		// 占2字节
        };
        explicit_atomic<preopt_cache_t *> _originalPreoptCache;		// 指针占8字节
    };
}

由此分析得出cache_t的内容带下等于8+8共占16字节

所以我们现在就可以通过内存平移来查看bits的内容了,bits前边我们共需要平移8+8+16=32个字节

bits构成

class_data_bits_t

首先通过objc_class结构体我们可以看到bitsclass_data_bits_t的结构体,我们先看一下class_data_bits_t的结构体源码

struct class_data_bits_t {
    friend objc_class;

    // Values are the FAST_ flags above.
    uintptr_t bits;
private:
    bool getBit(uintptr_t bit) const
    {
        return bits & bit;
    }

public:
	// 关键内容-属性列表、方法列表、协议列表、成员变量等信息都存在这个结构体中
    class_rw_t* data() const {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }
};

我们可以看到class_data_bits_t结构体中的data()方法,该方法返回值为class_rw_t *的结构体指针,而我们类的属性列表、方法列表、协议列表、成员变量等信息全部都存放在这个结构体中,接下来我们继续探索class_rw_t这个结构体

class_rw_t

我们还是先看class_rw_t结构体的源码,我对源码进行了删减,留下主要我们要关注的部分

  • ro()方法会返回一个class_ro_t *的结构体指针,这个结构体中存储这成员变量信息;
  • methods()方法会返回method_array_t存储着方法列表信息;
  • properties()方法返回property_array_t存储着属性列表信息;
  • protocols()返回protocol_array_t存储着协议列表信息;
struct class_rw_t {
public:
    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};
        }
    }
};

list_array_tt

穿插说明一下list_array_tt其实是一个结构体数组,内部声明了arrary_t结构体,arrary_t中的Ptr<List>就是一个数组,所以我们返回的method_array_t、property_array_t、protocol_array_t都是数组,我们可以直接通过索引的方式获取数组的元素来查看存储的具体内容

template <typename Element, typename List, template<typename> class Ptr>
class list_array_tt {
    struct array_t {
        uint32_t count;
        Ptr<List> lists[0];

        static size_t byteSize(uint32_t count) {
            return sizeof(array_t) + count*sizeof(lists[0]);
        }
        size_t byteSize() {
            return byteSize(count);
        }
    };
}

method_array_t

我们来看源码method_t就是方法的类型

class method_array_t : 
    public list_array_tt<method_t, method_list_t, method_list_t_authed_ptr>
{
    typedef list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> Super;

 public:
    method_array_t() : Super() { }
    method_array_t(method_list_t *l) : Super(l) { }

    const method_list_t_authed_ptr<method_list_t> *beginCategoryMethodLists() const {
        return beginLists();
    }
    
    const method_list_t_authed_ptr<method_list_t> *endCategoryMethodLists(Class cls) const;
};

method_t

method_t中通过big结构体来存储方法选择器、参数类型、以及IMP地址

struct method_t {
    static const uint32_t smallMethodListFlag = 0x80000000;

    method_t(const method_t &other) = delete;

    // The representation of a "big" method. This is the traditional
    // representation of three pointers storing the selector, types
    // and implementation.
    struct big {
        SEL name;
        const char *types;
        MethodListIMP imp;
    };
}

property_array_t

我们来看源码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) { }
};

property_t

property_t结构体中只有name名称和attributes两个变量

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

class_ro_t中ivars存储这属性信息,类型时ivar_list_t *结构体指针

class_ro_t

struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;
#ifdef __LP64__
    uint32_t reserved;
#endif

    union {
        const uint8_t * ivarLayout;
        Class nonMetaclass;
    };

    explicit_atomic<const char *> name;
    // With ptrauth, this is signed if it points to a small list, but
    // may be unsigned if it points to a big list.
    void *baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;

    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;
}

窥探bits

知识点补充

  1. 结构体类型调用方法可以直接用点语法进行调用
  2. 指针类型调用方法需要用“->”进行调用
  3. p *指针可以查看地址的内容

Test文件结构

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@protocol TestDelegate <NSObject>

- (void) testProtocol;

@end

@interface Test : NSObject<TestDelegate>

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

- (void) test;
+ (void) classMethod;

@end

NS_ASSUME_NONNULL_END

窥探步骤

  1. 首先通过p/x查看对象的首地址

  2. 地址平移32位得到bits的地址

  3. 打印bits地址内容,并进行强制转换为class_data_bits_t *结构体指针

  4. 调用data()查看class_rw_t *结构体指针

  5. 调用methods查看方法列表

  6. 调用properties查看属性列表

  7. 调用ro()方法查看class_ro_t *结构体指针

  8. 调用ivars查看成员变量信息

先找到bits

通过data()方法获取并查看class_rw_t

调用method()查看方法列表

调用properties()查看属性列表

调用ro()获取class_ro_t和ivars

类方法呢?

上边我们通过class_rw_t获取方法列表,但是这个方法列表中只有实例方法信息,我们并没有看到类的方法,那么类方法在哪里存放呢?先给大家一个结论,实例方法是存放在类对象的方法列表中的,但是类方法是存放在类的原类对象的方法列表中。

下面我们来通过代码来证实一下:

  1. 获取类方法的地址和内存排布
  2. 通过类的isa指针获取到类的原类
  1. 在获取原类的class_data_bits_t
  2. 在通过data()方法获取class_rw_t
  1. 在通过methods获取类方法列表

面试题扩展

class_getInstanceMethod和class_getClassMethod

class_getInstanceMethod:返回类的实例方法

class_getClassMethod:返回类的的类方法

我们来看一下下面代码分别打印什么:

// 类中的方法声明
// - (void) sayHello;
// + (void) sayHappy;

Method method1 = class_getInstanceMethod(pClass, @selector(sayHello));			// YES
Method method2 = class_getInstanceMethod(metaClass, @selector(sayHello));		// NO
Method method3 = class_getInstanceMethod(pClass, @selector(sayHappy));			// NO
Method method4 = class_getInstanceMethod(metaClass, @selector(sayHappy));		// YES

Method method1 = class_getClassMethod(pClass, @selector(sayHello));				// NO
Method method2 = class_getClassMethod(metaClass, @selector(sayHello));			// NO
Method method3 = class_getClassMethod(pClass, @selector(sayHappy));				// YES
Method method4 = class_getClassMethod(metaClass, @selector(sayHappy));			// YES
  1. Method method4 = class_getInstanceMethod(metaClass, @selector(sayHappy));

此项能找到的原因是因为sayHappy是类方法,类方法存放在元类的方法列表中,那么元类的方法列表就是类方法,所以可以成功查询到sayHappy。

  1. Method method4 = class_getClassMethod(metaClass, @selector(sayHappy));

class_getClassMethod源码:

/***********************************************************************
* class_getClassMethod.  Return the class method for the specified
* class and selector.
**********************************************************************/
Method class_getClassMethod(Class cls, SEL sel)
{
    if (!cls  ||  !sel) return nil;

    return class_getInstanceMethod(cls->getMeta(), sel);
}

getMeta()源码:

Class getMeta() {
    if (isMetaClassMaybeUnrealized()) return (Class)this;
    else return this->ISA();
}

通过class_getClassMethod源码我们可以看到,其内部获取类方法实质是哪传入的类获取了一次元类,在再元类的方法列表中进行查找,所以当传入类时,首先获取到元类,在查找方法,所以返回成功很容易理解。那么为什么传入元类的时候获取类方法也会返回正确呢,这个秘密其实在getMeta()方法中,getMeta()内部其实是有一层判断的,如果传入的类已经是元类了,那么就不会继续获取元类,而是直接返回,那么最终就还是获取当前元类的方法列表了,所以可以查到类方法。

总结:

class_getInstanceMethod:如果传入类对象则查找实例方法,如果传入元类对象则查找类方法;

class_getClassMethod:传入类对象或元类对象,都会查找当前类的类方法;

isKindOfClass和isMemberOfClass的区别

我们先分别看一下isKindOfClass和isMemberOfClass的源码:

isKindOfClass

+ (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = self->ISA(); tcls; tcls = tcls->getSuperclass()) {
        if (tcls == cls) return YES;
    }
    return NO;
}

- (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = [self class]; tcls; tcls = tcls->getSuperclass()) {
        if (tcls == cls) return YES;
    }
    return NO;
}

源码说明

  • isKindOfClass类方法:

我们可以看到,tcls会先获取当前类的ISA,也就是获取元类,然后进行遍历比较,如果当前元类与传入的类一致,则返回YES,如果不一致则获取当前元类的父类,再次进行比较,直到比较到根元类NSObject时,再次获取superclass时会向前找一次,就会找到NSObject类,当再次获取Superclass时会返回null,返回NO。

  • isKindOfClass实例方法:

我们可以看到,tcls会先获取当前类的Class,也就是获取当前实例对象的类对象,然后进行遍历比较,如果当前类与传入的类一致,则返回YES,如果不一致则获取当前类的父类对象,再次进行比较,直到比较到根类NSObject时,再次获取Superclass时会返回null,返回NO。

总结:

  • isKindOfClass类方法比较的是元类,并且如果当前类是要比较的类的派生类的话,返回YES,否则返回NO;
  • isKindOfClass实例方法比较的是类,并且如果当前实例对象的类对象时要比较的类对象的派生类的话,返回YES,否则返回NO;

isMenberOfClass

+ (BOOL)isMemberOfClass:(Class)cls {
    return self->ISA() == cls;
}

- (BOOL)isMemberOfClass:(Class)cls {
    return [self class] == cls;
}

源码说明

  • isMemberOfClass类方法

获取当前类的ISA,也就是元类,与传入的类对象进行比较,只比较一次,一致就返回YES,不一致则返回NO;

  • isMemberOfClass实例方法

获取当前实例对象的类对象,与传入的类对象进行比价,只比较一次,一致就返回YES,不一致则返回NO;