4、iOS底层分析 - 类的结构分析

通常熟知定义

1、关于类

类的设计只关注三个东西:类名、属性和方法

拥有相同属性和行为的对象都可以抽象为一个类。类名是标识符的一种,需要符合规范,通常类名的第一个字母大写,且不能有下划线,如果有多个单词使用驼峰原则。在对方法进行类的划分中,一般采取的做法是谁最熟悉这个方法那么就把这个方法划分给谁。在OC中,对象对方法的调用称为消息机制,即向既定的对象发送了什么消息。

2、简单内存分析

类创建对象,每个对象在内存中都占据一定的存储空间,每个对象都有一份属于自己的单独的成员变量,所有的对象公用类的成员方法,方法在整个内存中只有一份,类本身在内存中占据一份存储空间,类的方法存储于此。

每个对象内部都默认有一个isa指针指向这个对象所使用的类。

[p eat];表示给p所指向的对象发送一条eat消息,调用对象的eat方法,此时对象会顺着内部的isa指针找到存储于类中的方法,执行。

isa是对象中的隐藏指针,指向创建这个对象的类。

类的结构底层分析

1、类的结构

先来通过源码看一下类的结构到底是设么样的。

struct objc_class : objc_object {
    // Class ISA;  //8 占用的空间,后面进行内存偏移读取bits的时候要用
    Class superclass; //8
    cache_t cache;      //16       // 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();
    }
下边是一些类似 setData 的方法就是省略了
    void setData(class_rw_t *newData) {
        bits.setData(newData);
    }
。。。。。。。

看完源码我们知道,类里面有这4个属性。

  1. Class isa

          isa 隐藏属性,isa指向类的元类(meta  class)继承自父类  objc_object。

          isa的指针是关联对象和类

struct objc_object {
    Class Nonnull isa OBJC_ISA_AVAILABILITY;
}

    2 . Class superclass

          父类  也就是指向继承类

    3 . cache_t cache

          从名字可以猜出是缓存(缓存的方法,是一个二维数组后面分析cache_t的时候具体分析)

struct cache_t {
    struct bucket_t *_buckets;  //8
    mask_t _mask;               //4
    mask_t _occupied;           //4

_buckets 是指针类型所以长度是8位,mask_t 查看源码可以知道是 int32 类型是4位

typedef uint32_t mask_t; 
struct bucket_t {
private:
    // IMP-first is better for arm64e ptrauth and no worse for arm64.
    // SEL-first is better for armv7* and i386 and x86_64.
#if __arm64__
    MethodCacheIMP _imp;
    cache_key_t _key;
#else
    cache_key_t _key;
    MethodCacheIMP _imp;
#endif

读取源码可看出cache 是存的方法,通过key value找到对应方法的imp

4 . class_data_bits_t  bits

          是个结构体,从整体分析来说这个地方是存储数据的但是具体是什么需要验证,继续分析

分析探索

1、属性

(1)、创建一个LGPerson的类,添加属性和方法

扩展知识:.m中方法没有实现

  1.  如果没有调用,编译时和运行时均不会有问题
  2. 如果调用了,编译时不会报错,运行时会崩溃
@interface LGPerson : NSObject{
    NSString *nickName;
}

@property (nonatomic, copy) NSString *name;

- (void)sayHello;
+ (void)sayHappy;

@end

(2)、通过内存偏移得到bits

按照内存对齐原则,我们用x/4gx打印得到的首地址偏移32个字节,就应该能得出bits的内容 ,也就是bits的理论地址。这里我们强转一下(强转成class_data_bits_t *),再次打印,终于打印出class_data_bits_t的结构体

 OC 中一般调用成员变量都是用   ->   例如: p * $4->ro  有些时候会有区分

struct objc_class : objc_object {
    // Class ISA; // 8
    Class superclass; // 8
    cache_t cache;    // 16 不是8         // 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();
    }

(3)、class_rw_t

通过上边打印可以看到 $7 的类型是class_rw_t,    class_rw_t为类中存储属性和方法的地方,看一下class_rw_t 的实现,返回的是bits.data(),我们这里调用一下data方法之后得出一个class_rw_t类型的指针,直接取值 p *$7,结果如下

打印得到的 (class_rw_t)$8

(4)、properties

使用p命令查看结果中properties的值,得到类型property_array_t(在源码的objc-runtime-new.h中有其定义),为一个二维数组,继续探索其内部list,进行 p $10.list

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

 public:
    property_array_t duplicate() {
        return Super::duplicate<property_array_t>();
    }
};

(5)、property_list_t

得到property_list_t类型(在源码的objc-runtime-new.h中有其定义)的$11,继承自entsize_list_tt

struct property_list_t : entsize_list_tt<property_t, property_list_t, 0> {
};
struct entsize_list_tt {
    uint32_t entsizeAndFlags;
    uint32_t count;
    Element first;
下边的不重要,两个int32类型肯定不可能是存储属性的,所以继续查看first

(6)、first

查看property_list_t 继承与 entsize_list_tt 在其内部发现first方法,尝试打印p$11->first,最后找到了属性name

说明类的属性是在 class_data_bits_t bits 里面存储的

2、成员变量

1、class_rw_t
找到了属性的存储之后,没有看到成员变量,既然属性是在 class_rw_t 中存的。我们之前应该遇到过如下情况

@interface LGPerson : NSObject{
    NSString *hobby;
    NSString *_nickName;
}

@property (nonatomic, copy) NSString *nickName;

如果属性(nickName)和成员变量带下划线(_nickName)同名的话,那么只会有一个存在。而且属性nickName可以用self.nickName 和 _nickName。所以猜测肯定是属性在底层生成了一个待下划线的成员变量(底层分析后面会进行分析,这里不扩展)。

看一下class_rw_t 的底层源码,感觉可定是在class_ro_t * ro 中存储

struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    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;

    char *demangledName;

到此我们可以知道属性和成员变量的存储地方分别是

class_rw_t

properties 属性的存储,

ro               成员变量的存储

通过名字可以判断 methods 应该存放方法继续验证

3、类的方法存储

(1)、实例方法

$2 是 class_rw_t ,读取 methods 。通过查看源码可以知道返回的存储的格式是

SEL 方法名     typer 方法类型        imp 方法对应实现

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

省略。。。。。。。
};

struct method_t {
    SEL name;
    const char *types;
    MethodListIMP imp;
。。。。。。。。。
(lldb) p $2.methods
(method_array_t) $12 = {
  list_array_tt<method_t, method_list_t> = {
     = {
      list = 0x0000000100002240
      arrayAndFlag = 4294976064
    }
  }
}
(lldb) p $12.list
(method_list_t *) $13 = 0x0000000100002240
(lldb) p *$13
(method_list_t) $14 = {
  entsize_list_tt<method_t, method_list_t, 3> = {
    entsizeAndFlags = 26
    count = 4
    first = {
      name = "sayHello"
      types = 0x0000000100001f8b "v16@0:8"
      imp = 0x0000000100001b90 (LGTest`-[LGPerson sayHello] at LGPerson.m:13)
    }
  }
}
(lldb) 

通过查看查看 methods 里面之后只有我们创建的实例方法和属性的set、get方法。那么类方法在哪呢?

重新分析一下  objc_object  

1)、superclass 我们调用类方法的时候是 例如:[LGPerson  sayHappy];这个样子调用的,父类名字跟子类名称都不一样,很明显不可能是在父类中。

2)、cache 看了cache_t 的源码,这个地方确实是更方法有关系,但是名称是缓存应该不是。

3)、bits bits我们刚看完 class_rw_t

properties 属性的存储,

ro               成员变量的存储

methods  存放类的实例方法

4)、isa 那么只剩下isa。前面我们知道isa是指向一个同名的元类,调用类方法也是用的类名,那么很有可能就是在元类中。探索一下

(2)、类方法的存储

同上x/4gx 打印出isa 的地址,然后获取元类

class_data_bits_t   ->data()      -     class_rw_t        -       methods,得到 list 数组,最后找到了我们定义的类方法sayHappy流程和查找类的实例方法一样,所以直接看结果

(lldb) p $13.list
(method_list_t *) $14 = 0x00000001000021d8
(lldb) p *$14
(method_list_t) $15 = {
  entsize_list_tt<method_t, method_list_t, 3> = {
    entsizeAndFlags = 26
    count = 1
    first = {
      name = "sayHappy"
      types = 0x0000000100001f8b "v16@0:8"
      imp = 0x0000000100001bc0 (LGTest`+[LGPerson sayHappy] at LGPerson.m:17)
    }
  }
}

总结

  1. 类的继承和isa指向见下图
    1. 类 – 父类 – 基类(NSObject class)- nil
    2. 类 – 元类 – 元类的父类 – 基类的元类(NSObject meta class)
  2. 其中类的属性和成员变量都存放在类的class_rw_t结构体中
    1. class_rw_t
      1. properties 属性的存储,
      2. ro               成员变量的存储
      3. methods  类的实例方法
  3. 属性的定义,还伴随着成员变量以及其getter和setter的自动生成
  4. 类的类方法,则以实例方法的形式,存放在元类中,而元类又是继承自NSObject,形成一个闭环

扩展

指针

  • int a; //这是一个普通的整型变量

  • int *p; //首先从P处开始,先与*结合,所以说明P是一个指针,然后再与int结合,说明指针所指向的内容的类型为int型,所以P是一个返回整型数据的指针

发布了83 篇原创文章 · 获赞 12 · 访问量 18万+

猜你喜欢

转载自blog.csdn.net/shengdaVolleyball/article/details/104033586