美文网首页
iOS底层原理探究 - 类结构分析

iOS底层原理探究 - 类结构分析

作者: JasonL | 来源:发表于2021-06-29 00:36 被阅读0次

前言

等风来不如追风去,总有那么一个人在这风景正好的季节来到你的身边。在上一篇探究了对象的本质和isa指针的底层,那么我们继续来看的底层结构。

补充知识

  • 在OC环境下使用的类,在底层都有替换的类去实现
底层实现.png

isa 和类的关联

类的isa

探索对象的时候我们已经知道,对象的结构体中的isa指向的是,这个时候就会想,也是一个对象,中也有isa,那么isa又指向哪里呢?如下图:

类和isa的关联.png 从图中可以看出,JCPerson类对应了两个内存地址0x00000001000085f00x00000001000085c8,哪一个才是JCPerson的地址呢?一个在内存中存在几个内存地址呢?接下来看下图: 类的地址.png
看到这里应该非常清晰了,一个只有一个内存地址,0x00000001000085f0JCPerson的类地址,而0x00000001000085c8这个类地址苹果把它叫做元类
总结:
  • 元类由系统编译器自动生成和编译,与创建者无关
  • 对象isa指向类对象isa指向元类

isa的走位图

上面我们已经分析了对象isa的走向,那么元类isa又指向哪里呢?结合LLDB来进行探索。

LLDB调试isa走位图.png 从图中可以得到:
  • 对象 isa --> 类 isa --> 元类 isa --> 根元类isa --> 根元类(自己)
  • 根类(NSObject) isa --> 根元类 isa --> 根元类(自己)
isa的流程图:
isa走位图.png

类、元类、根元类继承图

创建JCPerson,JCTeacher,NSObject的相关代码,探究一下它们的继承关系。

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSLog(@"%@",class_getSuperclass(JCTeacher.class));
        NSLog(@"%@",class_getSuperclass(JCPerson.class));
        NSLog(@"%@",class_getSuperclass(NSObject.class));
        # NSObject实例对象
        NSObject *object1 = [NSObject alloc];
        # NSObject类
        Class class = object_getClass(object1);
        # NSObject元类
        Class metaClass = object_getClass(class);
        # NSObject根元类
        Class rootMetaClass = object_getClass(metaClass);
        # NSObject根根元类
        Class rootRootMetaClass = object_getClass(rootMetaClass);
        NSLog(@"\nNSObject实例对象  %p\nNSObject类  %p\nNSObject元类  %p\nNSObject根元类  %p\nNSObject根根元类  %p",object1,class,metaClass,rootMetaClass,rootRootMetaClass);
        # NSObject 根类特殊情况
        Class nsuperClass = class_getSuperclass(NSObject.class);
        NSLog(@"%@ - %p",nsuperClass,nsuperClass);
        # 根元类 -> NSObject
        Class rnsuperClass = class_getSuperclass(metaClass);
        NSLog(@"%@ - %p",rnsuperClass,rnsuperClass);
        # JCPerson元类和元类的父类
        Class pMetaClass = object_getClass(JCPerson.class);
        Class psuperClass = class_getSuperclass(pMetaClass);
        NSLog(@"%@ - %p",psuperClass,psuperClass);
        # JCTeacher元类和元类的父类
        Class tMetaClass = object_getClass(JCTeacher.class);
        Class tsuperClass = class_getSuperclass(tMetaClass);
        NSLog(@"%@ - %p",tsuperClass,tsuperClass);
    }
    return 0;
}
类、元类的继承关系.png
源码分析中知道,NSObject的父类打印结果是nilNSObject根元类的父类的地址等于NSObject类的地址;JCPerson元类的父类的地址等于NSObject的元类。
  • JCTeacher-->JCPerson-->NSObject-->nil
  • JCTeacher元类-->JCPerson元类-->NSObject元类-->NSObject根元类-->NSObject
的继承图:
类的继承图.png
isa流程图和继承链:
isa流程图和继承图.png

内存偏移

在前面探究对象的底层实现,我们了解到对象属性getter方法底层实现是通过首地址+内存偏移的方式去获取内存中的变量。接下来我们来看一下内存偏移

基本指针
# 普通指针
int a = 10; //
int b = 10; //
JCNSLog(@"%d -- %p",a,&a);
JCNSLog(@"%d -- %p",b,&b);
==========: 10 -- 0x7ffeefbff4ec
==========: 10 -- 0x7ffeefbff4e8
  • a的地址是0x7ffeefbff4ecb的地址是0x7ffeefbff4e8,相差4个字节,int类型是4个字节的长度
  • a>b的地址,从高地址低地址偏移,这符合栈内存的分配原则
对象指针
# 对象
JCPerson *p1 = [JCPerson alloc];
JCPerson *p2 = [JCPerson alloc];
JCNSLog(@"%@ -- %p",p1,&p1);
JCNSLog(@"%@ -- %p",p2,&p2);
==========: <JCPerson: 0x1004075a0> -- 0x7ffeefbff4e8
==========: <JCPerson: 0x100408570> -- 0x7ffeefbff4e0
  • alloc开辟的内存在堆区指针地址栈区
  • 堆区是从地址 --> 地址,栈区是从地址 --> 地址
数组指针
int c[4] = {1,2,3,4};
int *d   = c;
JCNSLog(@"%p - %p - %p",&c,&c[0],&c[1]);
JCNSLog(@"%p - %p - %p",d,d+1,d+2);

for (int i = 0; i<4; i++) {
     int value =  *(d+i);
     JCNSLog(@"----%d",value);
}
==========: 0x7ffeefbff4e0 - 0x7ffeefbff4e0 - 0x7ffeefbff4e4
==========: 0x7ffeefbff4e0 - 0x7ffeefbff4e4 - 0x7ffeefbff4e8
==========: ----1
==========: ----2
==========: ----3
==========: ----4
  • 数组的地址就是元素的首地址,即&c == &c[0]
  • 数组中每个元素的地址可以通过:首地址 + n*元素类型大小来获取,只需要数组中元素数据类型相同
  • 数组中的每个元素的地址间隔是通过当前元素的数据类型决定的
总结:
  • 内存偏移可以根据首地址 + 偏移值方式来获取各个数据的内存地址

类结构的分析

上面我们已经了解了内存偏移的知识,接下来我们来探究的底层结构。

类的内存地址.png 代码分析:在对象底层结构中存放在属性成员变量等数据;从图中打印可以看出是有内存的,那么它里面存放着什么呢?接下来分析底层的数据结构。

类的底层结构:

探索对象isa的过程中,我们已经知道isa在底层是Class类型,Class类型是objc_class *,所有的底层实现都是objc_class,通过全局搜索可以找到如下代码:

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不可用

通过上面这段代码我们发现在OBJC2中不可用,而现在的版本基本是在用OBJC2,所以这不是我们分析的,接下来在看一下如下代码:

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 ISA;
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
    ...
    下面全部是方法,不需要分析,省略
}

源码分析:objc_class是继承objc_object,这说明也是对象,所谓万物皆对象。objc_class里面有一个隐藏成员变量isa,我们前面已经分析过了,下面还有三个成员变量superclass,cache,bits,我们知道首地址就是isa,那么我们可以通过首地址+偏移量的方式去获取成员变量的地址,然后获取值。

  • isa是结构体指针,占8字节
  • Class superclassClass类型,也是属于结构体指针,占8字节
  • cachecache_t结构体,结构体大小由内部的变量决定
  • bitsclass_data_bits_t结构体,如果知道前面三个成员变量的大小,那么就可以得到bits的地址

前面三个成员变量已经知道了前两个的大小,只要知道cache的内存大小,接下来看一下cache_t的内存大小

typedef unsigned long           uintptr_t;
#if __LP64__
typedef uint32_t mask_t;  // x86_64 & arm64 asm are less efficient with 16-bits
#else
typedef uint16_t mask_t;
#endif

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
    };
   ...
   下面的一些方法直接省略(static类型的是在内存的全局区,不在结构体里面的内存)

}

cache_t是一个结构体类型,内部包含了_bucketsAndMaybeMask和一个联合体.

  • _bucketsAndMaybeMaskuintptr_t类型,而uintptr_t是无符号长整型占8个字节
  • 联合体内存大小由成员变量中的最大变量的内存大小决定,该联合体由一个结构体_originalPreoptCache两个成员变量组成,由于联合体存在互斥的,所以只需要得到其中最大变量的内存大小
  • _originalPreoptCachepreopt_cache_t *结构体指针类型,占8个字节
  • 结构体中有_maybeMask,_flags,_occupied_maybeMaskmask_t类型,mask_t又是uint32_t类型,占4个字节,_flags_occupieduint16_t类型,占2个字节

综上所述cache_t的内存大小为16字节。

总结:
  • isa内存地址为首地址
  • superclass地址为首地址+0x08
  • cache_t地址为首地址+0x10
  • bits地址为首地址+0x20
    类的结构图.png

bits数据结构

上面已经了解的基本结构,isasuperclass已经探究过了,接下来我们先来研究一下成员变量bits存储了哪些信息?

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 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 getSuperclass() const {...}
    void setSuperclass(Class newSuperclass) {...}

    class_rw_t *data() const {
        return bits.data();
    }
    void setData(class_rw_t *newData) {
        bits.setData(newData);
    }

bitsclass_data_bits_t类型,底层源码中还有一个data(),返回bits.data(),这有可能就是bits中存储的数据。data()的类型是class_rw_t

struct class_rw_t {
    ... //省略一些没用的方法
    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);
    }

    void set_ro(const class_ro_t *ro) {
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            v.get<class_rw_ext_t *>(&ro_or_rw_ext)->ro = ro;
        } else {
            set_ro_or_rwe(ro);
        }
    }
    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是一个结构体,里面存储着方法,属性,协议列表,接下来验证是否存储在class_rw_t中。

属性探究( properties() )
@interface JCPerson : NSObject{
    NSString *subject;
}
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *hobby;

- (void)sayNB;
+ (void)say666;
@end
类结构中属性分析.png
  • property_list_t中存储name,hobby属性
  • p $7.get(2)会提示数组越界,没有找到成员变量subject

问题:那么定义的subject成员变量存到哪里去了呢?

补充:成员变量

class_rw_t中除了有属性,方法,协议以外,还有class_ro_t结构体指针类型的ro(),在class_ro_t结构体中我们可以找到ivar_list_t类型的指针ivars成员变量会不会存在这里呢?

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;
类结构中成员变量分析.png
源码和LLDB分析:
  • 成员变量底层实现是ivar_t,存储在class_ro_t成员变量列表
  • 系统是自动给属性添加_属性名的变量,存储在class_ro_t成员变量列表
方法探究( methods() )
类结构的方法分析.png
通过LLDB的方式,可以得到定义的方法。但是发现使用get(index)的方式无法得到,�使用get(index).big()才能获取,这是为什么呢?
struct property_t {
    const char *name;
    const char *attributes;
};

struct method_t {
    struct big {
        SEL name;
        const char *types;
        MethodListIMP imp;
    };
    big &big() const {
        ASSERT(!isSmall());
        return *(struct big *)this;
    }

    SEL name() const {
        if (isSmall()) {
            return (small().inSharedCache()
                    ? (SEL)small().name.get()
                    : *(SEL *)small().name.get());
        } else {
            return big().name;
        }
    }
    const char *types() const {
        return isSmall() ? small().types.get() : big().types;
    }
    IMP imp(bool needsLock) const {
        if (isSmall()) {
            IMP imp = remappedImp(needsLock);
            if (!imp)
                imp = ptrauth_sign_unauthenticated(small().imp.get(),
                                                   ptrauth_key_function_pointer, 0);
            return imp;
        }
        return big().imp;
    }
源码分析: 属性和方法获取区别.png
  • 属性底层实现是property_t,在property_t结构体中定义了name等变量
  • 方法底层实现是method_t,在method_t结构体中定义了一个big(),通过big()获取SELIMP

接下来我们继续打印methods,如下图。

类结构的方法分析2.png
  • method_list_t中有对象方法,属性的setter方法getter方法
  • method_list_t中没有获取到类方法

问题:那么类方法存储到哪里去了呢?

补充:类方法

对象方法存储在中,那类方法可能存储在元类

类方法的分析.png
  • object_getClass获取到JCPerson的元类
  • 元类中method_list_t中存储着类方法
总结:
  • 类的结构主要由isa,superclass,cache,bits组成
  • bits中存储着属性,方法,协议
  • 属性存储在property_list_t中,而成员变量存储在class_ro_t-->ivar_list_t,系统为属性自动生成的_属性名的变量也存储在class_ro_t-->ivar_list_t
  • 方法存储在method_list_t中,method_list_t主要存储着对象方法,属性的setter方法getter方法,而类方法存储在元类中的method_list_t

相关文章

网友评论

      本文标题:iOS底层原理探究 - 类结构分析

      本文链接:https://www.haomeiwen.com/subject/qaxsyltx.html