iOS 类原理探索:类的结构分析

2021-07-17  本文已影响0人  SpringSunLcy

OC 类原理探索 系列文章

  1. OC 类原理探索:类的结构分析
  2. OC 类原理探索:类结构分析补充
  3. OC 类原理探索:属性的底层原理

前言

上一篇 OC 对象原理探索(三):对象的本质 & isa,介绍了isa的结构,关联到了类,这篇文章主要对类的结构进行分析。

一、isa 分析到元类

先通过lldb调试进行一波探索:

image.png

如上图:通过isa & isa的掩码,最终得到了SSLPerson,但是类中的isa & isa的掩码也得到了SSLPerson这是为什么呢?

是不是内存中存了很多个SSLPerson类对象呢,下面我们来验证一下:

Class class1 = [SSLPerson class];
Class class2 = [SSLPerson alloc].class;
Class class3 = object_getClass([SSLPerson alloc]);
Class class4 = [SSLPerson alloc].class;
NSLog(@"\n%p-\n%p-\n%p-\n%p-",class1,class2,class3,class4);

打印结果:
0x1000081f0-
0x1000081f0-
0x1000081f0-
0x1000081f0-

打印的地址都是相同的,可以判断SSLPerson类对象在内存中只有一个。

我们用MachOView打开本项目的MachO文件 ->Symbol Table->*Symbols-> 搜索框输入class -> 向下滑:

106931623940431_.pic_hd.jpg

二、isa 走位图和继承链

1. isa 的走位链

SSLPerson走位打印情况:

// SSLPerson 实例对象
SSLPerson *person = [SSLPerson alloc];
// SSLPerson 类对象
Class class = object_getClass(person);
// SSLPerson 元类
Class metaClass = object_getClass(class);
// SSLPerson 根元类
Class rootMetaClass = object_getClass(metaClass);
// SSLPerson 根根元类
Class rootRootMetaClass = object_getClass(rootMetaClass);

NSLog(@"\n实例对象:%p \n类:%p \n元类:%p \n根元类:%p \n根根元类:%p",person,class,metaClass,rootMetaClass,rootRootMetaClass);

打印结果:
实例对象:0x101044470 
类:0x1000081f0 
元类:0x1000081c8 
根元类:0x7fff80843fe0 
根根元类:0x7fff80843fe0

看打印结果,根元类根根元类的地址都是0x7fff80843fe0。可以得出SSLPersonisa指向关系:实例isa -> 类 isa -> 元类 isa -> 根元类 isa -> 根元类

NSObject走位打印情况:

// NSObject 实例对象
NSObject *object = [NSObject alloc];
// NSObject 类
Class class = object_getClass(object);
// NSObject 元类
Class metaClass = object_getClass(class);
// NSObject 根元类
Class rootMetaClass = object_getClass(metaClass);
// NSObject 根根元类
Class rootRootMetaClass = object_getClass(rootMetaClass);
    
NSLog(@"\n实例对象:%p \n类:%p \n元类:%p \n根元类:%p \n根根元类:%p ",object,class,metaClass,rootMetaClass,rootRootMetaClass);

打印结果:
实例对象:0x100562780 
类:0x7fff80844008 
元类:0x7fff80843fe0 
根元类:0x7fff80843fe0 
根根元类:0x7fff80843fe0 

看打印结果,元类根元类根根元类的地址都是0x7fff80843fe0。可以得出NSObjectisa指向关系:实例isa -> 类 isa -> 根元类 isa -> 根元类。可以发现NSObject跟普通类有所不同,isa指针直接指向了根元类

根据上面的分析可以得到isa的走位图:

image.png

2. 继承链

看下面的打印结果:

// SSLStudent 元类
Class tMetaClass = object_getClass(SSLStudent.class);
// SSLStudent 元类的父类
Class tSuperClass = class_getSuperclass(tMetaClass);
NSLog(@"SSLStudent 元类:%p",tMetaClass);
NSLog(@"SSLStudent 元类的父类:%p",tSuperClass);
    
// LGPerson 元类
Class pMetaClass = object_getClass(SSLPerson.class);
// LGPerson 元类的父类
Class pSuperClass = class_getSuperclass(pMetaClass);
NSLog(@"SSLPerson 元类:%p",pMetaClass);
NSLog(@"SSLPerson 元类的父类:%p",pSuperClass);
    
// NSObject 元类
Class nMetaClass = object_getClass(NSObject.class);
// NSObject 元类的父类
Class nSuperClass = class_getSuperclass(nMetaClass);
NSLog(@"NSObject 元类:%p",nMetaClass);
NSLog(@"NSObject 元类的父类:%p",nSuperClass);

// NSObject 父类
Class superClass = class_getSuperclass(NSObject.class);
NSLog(@"NSObject :%p",NSObject.class);
NSLog(@"NSObject 父类:%p",superClass);

打印结果:
SSLStudent 元类:0x100008178
SSLStudent 元类的父类:0x1000081c8
SSLPerson 元类:0x1000081c8
SSLPerson 元类的父类:0x7fff80843fe0
NSObject 元类:0x7fff80843fe0
NSObject 元类的父类:0x7fff80844008
NSObject :0x7fff80844008
NSObject 父类:0x0

因此可以得到类的继承链:

image.png

3.官方走位继承图

isa流程图.png

三、源码分析类的结构

我们打开源码(objc4-818版本)搜索struct objc_class查看类结构。

1. 废弃的类结构

struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class _Nullable super_class                              OBJC2_UNAVAILABLE;
    const char * _Nonnull name                               OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;

在网上我们也会经常看到这段代码,但是我们可以看到OBJC2_UNAVAILABLE,说明这个版本在2.0中已经废弃了,我们就不用再看这个版本。

2. 源码类结构

现在用的类结构版本在objc-runtime-new.h中,定义如下:

struct objc_class : objc_object {
    xxx...
    // 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() const {
        return bits.data();
    }
    xxx...
}

可以看到类是一个objc_class类型的结构体,继承于objc_object,我们之前文章已经了解到objc_object结构体中有个isa_t类型的isa成员变量。

objc_class中的成员变量:

四、内存偏移(知识补充)

// 基本类型
int a = 10;
// 对象类型
SSLPerson *person = [SSLPerson alloc]; 
NSLog(@"%p -- %d",&a,a);
NSLog(@"%p -- %@",&person,person);

打印结果:
0x7ffeefbff3ac -- 10
0x7ffeefbff3a0 -- <SSLPerson: 0x100728a20>

a指针指向10的地址,person指针指向[SSLPerson alloc]开辟的内存地址,同时&person指针又指向了person指针的地址。

如图:

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

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

打印结果:
0x7ffeefbff3a0 - 0x7ffeefbff3a0 - 0x7ffeefbff3a4
0x7ffeefbff3a0 - 0x7ffeefbff3a4 - 0x7ffeefbff3a8
1
2
3
4
image.png

五、类结构的内存计算

1. 计算 bits 偏移大小

上面我们知道类结构中有isasuperclasscachebits四个成员变量。isa8个字节,superclass是结构体指针也占8个字节,下面来看下cache占多少个字节。

struct cache_t {

private:
    explicit_atomic<uintptr_t> _bucketsAndMaybeMask; // 8
    union {
        struct {
            explicit_atomic<mask_t>    _maybeMask;// 4
#if __LP64__
            uint16_t                   _flags;  // 2
#endif
            uint16_t                   _occupied; // 2
        };
        explicit_atomic<preopt_cache_t *> _originalPreoptCache;// 指针 8
    };
}

所以得到,从类的首地址偏移到bits需要8 + 8 + 16 = 32个字节也就是0x20

2. 通过偏移获取 bits

lldb进行调试获取bitsclass_rw_t的值:

image.png

3. bts 中 class_rw_t 结构分析

接下来我们要探究class_rw_t方法属性的存储,打开源码,看一下class_rw_t中有些什么:

struct class_rw_t {
    xxx...
    
    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};
        }
    }
}

看下method_array_tproperty_array_t的定义:

class method_array_t : 
    public list_array_tt<method_t, method_list_t, method_list_t_authed_ptr>
{
    xxx...
};

class property_array_t : 
    public list_array_tt<property_t, property_list_t, RawPtr>
{
    xxx...
};

template <typename Element, typename List, template<typename> class Ptr>
class list_array_tt {
    xxx...
}

struct property_list_t : entsize_list_tt<property_t, property_list_t, 0> {
};
struct property_t {
    const char *name;
    const char *attributes;
};

struct method_list_t : entsize_list_tt<method_t, method_list_t, 0xffff0003, method_t::pointer_modifier> {
    xxx...
}
struct method_t {
    xxx...
    struct big {
        SEL name;
        const char *types;
        MethodListIMP imp;
    };
    xxx...
}

template <typename Element, typename List, uint32_t FlagMask, typename PointerModifier = PointerModifierNop>
struct entsize_list_tt {
    xxx...
    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);
    }
    xxx...
}

4. lldb 查看属性列表:property_list_t

image.png

properties()函数,最终得到了属性nameage,并没有获取到_hoby成员变量;

5. lldb 查看方法列表:method_list_t

image.png

methods()函数,最终得到了方法namesetNameagesetAgeeat,并没有获取到+ (void)run

6. lldb 查找类方法

类方法存储在元类中,lldb验证:

image.png

7. 成员变量结构分析

image.png

8. lldb 查看成员变量:ivar_list_t

image.png
上一篇下一篇

猜你喜欢

热点阅读