美文网首页
类的结构分析

类的结构分析

作者: _Luyouli | 来源:发表于2020-09-13 17:25 被阅读0次

我们在上一节isa的结构分析分析了isa的结构,我们在创建一个类的时候打印其地址得到的第一个地址就是它的isa地址,我们知道所有的类都有一个isa指向它的本质,那么除了isa,类里面的结构还有什么内容呢?我们这一节就来分析类的结构

实例对象、类、元类分析

在探究类的结构之前我们先来分析一下实例对象,类和元类的关系
首先我们创建一个类如下:

#import <Foundation/Foundation.h>
#import "SYPerson.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...

        SYPerson *person = [[SYPerson alloc]init];
        
        NSLog(@"%p",person);
    }
    return 0;
}

x/4gx: 以16进制形式打印地址内容,读取4个16字节内容
p/x: 打印变量的十六进制格式信
po: 打印变量的 description 方法
通过isa & ISA_MSAK可以查看 isa 指向的类信息
# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL
...
# elif __x86_64__
#   define ISA_MASK        0x00007ffffffffff8ULL

我们通过打印分析isa走位

isa指向打印

分析:

  • 实例对象person的isa指向了SYPerson
  • SYPerson的isa指向了SYPerson元类
  • 元类SYPerson的isa指向了NSObject
  • NSObject的isa指向了本身
    那么得到的isa走位符合下图
    isa流程图

查看类结构源码

要分析类SYPerson我们知道它继承来自父类NSObject,所以我们通过查看NSObject的源码找到了:

@protocol NSObject

- (BOOL)isEqual:(id)object;
@property (readonly) NSUInteger hash;

@property (readonly) Class superclass;

这里我们可以看到NSObject有一个Class类型superclass,再点进去发现typedef struct objc_class *Class; 说明Class是一个名为objc_class的结构体,也就是说类的本质就是一个结构,那么我们想分析类结构实质上就是分析这个结构体,我们可以看到结构体objc_class的源码发现有以下成员:

struct objc_class : objc_object {
    // 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
    ...
}

前面我们说过所有的类都有isa,为什么这里没有呢?那是因为objc_class继承来自objc_object,而objc_object里面已经包含了isa

struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};

这里也说明所有以objc_object为模板创建的类都有isa

通过地址平移分析objc_class结构

  • 地址平移

什么是地址平移呢?我们可以拿到类的地址后通过类的地址平移拿到里面的所有信息。通过下面例子我们可以了解一下地址平移

        int a[4] = {1,2,3,4};
        int *b = a;
        NSLog(@"%p--%p--%p",&a,&a[0],&a[1]);
        NSLog(@"%p--%p--%p",b,b+1,b+2);
        
        for (int i = 0; i < 4; i++) {
            int value = *(b + i);
            NSLog(@"%d",value);
        }
//打印结果
2020-09-13 15:54:53.066933+0800 KCObjc[61247:1111510] 0x7ffeefbff510--0x7ffeefbff510--0x7ffeefbff514
2020-09-13 15:54:53.067769+0800 KCObjc[61247:1111510] 0x7ffeefbff510--0x7ffeefbff514--0x7ffeefbff518
2020-09-13 15:54:53.067892+0800 KCObjc[61247:1111510] 1
2020-09-13 15:54:53.067976+0800 KCObjc[61247:1111510] 2
2020-09-13 15:54:53.068055+0800 KCObjc[61247:1111510] 3
2020-09-13 15:54:53.068130+0800 KCObjc[61247:1111510] 4

数组a的第一个元素的地址就是数组的地址,通过对数组的地址进行便宜依次得到了数组后续的值。

在知道什么是地址偏移后我们再通过地址偏移来分析objc_class的结构。前面我们知道了结构体中的成员包含了:

  • Class ISA继承自父类,占8字节
  • Class superclass是一个指针,占用8个字节。
  • cache_t cache以前缓存指针和虚函数表,占16字节
  • class_data_bits_t bits结构体
    那么我们类的信息是否存在于这里bits呢?我们通过查看结构体源码发现
    class_rw_t* data() const {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }

查看结构体class_rw_t发现:

    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 *>()->methods;
        } else {
            return method_array_t{v.get<const class_ro_t *>()->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 *>()->properties;
        } else {
            return property_array_t{v.get<const class_ro_t *>()->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 *>()->protocols;
        } else {
            return protocol_array_t{v.get<const class_ro_t *>()->baseProtocols};
        }
    }

这些代码是否看着很熟悉呢?有methodspropertiesprotocols,我们可以进行验证这是否可以得到们类的信息。给我们的person添加一个方法- (void)helloClass;
前面我们分析类地址偏移可以得到类里面的所有信息,想查看bits的信息我们需要将地址偏移8+8+18=32字节.

(lldb) x/4gx SYPerson.class
0x1000021f0: 0x00000001000021c8 0x0000000100334140
0x100002200: 0x0000000101134640 0x0002801000000003
//0x1000021f0地址偏移32得到0x100002210
(lldb) p (class_data_bits_t *)0x100002210
(class_data_bits_t *) $1 = 0x0000000100002210
//调用data()
(lldb) p $1->data()
(class_rw_t *) $2 = 0x00000001011345e0
//获取data()方法得到的数据class_rw_t类型的$3
(lldb) p *$2
(class_rw_t) $3 = {
  flags = 2148007936
  witness = 1
  ro_or_rw_ext = {
    std::__1::atomic<unsigned long> = 4294975744
  }
  firstSubclass = nil
  nextSiblingClass = NSUUID
}
//调用前面查看到方法如methods()
(lldb) p $3.methods()
(const method_array_t) $5 = {
  list_array_tt<method_t, method_list_t> = {
     = {
      list = 0x0000000100002148
      arrayAndFlag = 4294975816
    }
  }
}
//查看list里面数据
(lldb) p $5.list
(method_list_t *const) $6 = 0x0000000100002148
//获取到$6的内容,得到了我们申明的方法helloClass
(lldb) p *$6
(method_list_t) $7 = {
  entsize_list_tt<method_t, method_list_t, 3> = {
    entsizeAndFlags = 26
    count = 1
    first = {
      name = "helloClass"
      types = 0x0000000100000f9e "v16@0:8"
      imp = 0x0000000100000f10 (KCObjc`-[SYPerson helloClass])
    }
  }
}
//我们添加属性后同理也可获得属性名称

总结

我们首先通过分析isa走位分析了实例对象、类、元类的关系,然后通过查看源码找到了了类的结构体objc_class,明确了类的底层结构就是结构体,进而对结构体里面的成员进行了分析,发现类的属性方法都存储在class_data_bits_t bits的结构体中,通过地址偏移也验证了这个结论。

相关文章

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

    OC 类原理探索 系列文章 OC 类原理探索:类的结构分析 OC 类原理探索:类结构分析补充[https://ju...

  • 多线程基础(十三):java中的FutureTask

    [toc] FutureTask源码分析 1.类结构及常量、变量 1.1 类结构 FutureTask类结构如下:...

  • iOS类结构:cache_t分析

    一、cache_t 内部结构分析 1.1 在iOS类的结构分析中,我们已经分析过类(Class)的本质是一个结构体...

  • 类,类结构分析

    忙不是不学习的借口 在isa和类的关联[https://www.jianshu.com/p/079a6ad90f1...

  • iOS-OC底层04:类结构分析

    类结构分析 通过lldb来分析类结构 查看objc2的内存情况 类对象只有一份,isa对象-> 类(LGPerso...

  • iOS-底层分析之类的结构分析

    类的结构分析 本文主要分析iOS中的类以及类的结构,下面我们通过一个例子来探索类的结构 我们定义一个WPerson...

  • 类结构分析

    这片文章主要分析的是类的结构以及对象-类-元类-根元类之间的走位. 一. isa的指向以及类之间的关系 准备工作定...

  • 类结构分析

    类结构分析 回顾 前面我们讲了alloc 流程中对象的创建过程,下面我们来探索一下类的结构,废话不多说,开始~ 类...

  • 类结构分析

    开发中经常创建一个 TestClass.h 和 TestClass.m 文件,而这个 TestClass 就是我们...

  • 类的结构分析

    神图镇楼,相信做过iOS开发的同学一定非常熟悉这张经典图,每次看这张图都有不一样的体会,今天我们就借这张图,引出我...

网友评论

      本文标题:类的结构分析

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