美文网首页
类的结构分析

类的结构分析

作者: Y丶舜禹 | 来源:发表于2020-09-14 10:53 被阅读0次
isa走位及继承关系图

神图镇楼,相信做过iOS开发的同学一定非常熟悉这张经典图,每次看这张图都有不一样的体会,今天我们就借这张图,引出我们对的探索,主要探索的是 类的结构分析以及isa的走向和 继承.

准备工作

objc781源码
创建两个类
ZGPerson继承NSObject,定义一些方法和属性

@interface ZGPerson : NSObject{
    NSInteger age;
}
@property (nonatomic, copy) NSString *name;
- (void) say666;
+ (void) say888;
@end

- (void)say666
{}
+ (void)say888
{}
@end

ZGTeacher继承ZGPerson

@interface ZGTeacher : ZGPerson

@end

输出两个对象

int main(int argc, char * argv[]) {
    NSString * appDelegateClassName;
    @autoreleasepool {
        
        ZGPerson *person = [ZGPerson alloc];
        ZGTeacher *teacher = [ZGTeacher alloc];
    
        NSLog(@"person %@\n teacher %@",person,teacher);
        // Setup code that might create autoreleased objects goes here.
        appDelegateClassName = NSStringFromClass([AppDelegate class]);
    }
    return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
LLDB调试

利用LLDB我们打印内存,先介绍几个常用指令

x/4gx: 以16进制形式打印地址内容,读取416字节内容
p/x: 打印变量的16进制格式
po: 打印变量的 description 方法

调试结果如下图所示

LLDB调试内存信息

在我们调试的过程中发现p/x 0x001d8001000012d5 & 0x00007ffffffffff8ULLp/x 0x00000001000012a8 & 0x00007ffffffffff8ULL中打印的信息都是ZGPerson,但是两者的含义却不相同。

  • 0x001d8001000012d5person对象isa指针所存储的的信息
  • 0x00000001000012a8 是isa中获取的类信息所指的类的isa的指针地址,即ZGPerson类的isa指针地址,我们称ZGPerson这样的类的类为元类
元类

什么是元类?,主要有以下几点说明:

  • 对象的isa 是指向类,类的其实也是一个对象,可以称为类对象,其isa的位域指向苹果定义的元类
  • 元类是系统给的,其定义和创建都是由编译器完成,在这个过程中,的归属来自于元类
  • 元类是类对象 的类,每个类都有一个独一无二元类用来存储 类方法的相关信息。
  • 元类本身是没有名称的,由于与类相关联,所以使用了同类名一样的名称
isa指针指向
isa指针指向

由上图中LLDB调试信息我们得出isa的指向:

  • person实例对象的 isa指向了 ZGPerson
  • ZGPerson类对象的isa 指向了 ZGPerson 元类
  • ZGPerson 元类对象的 isa 指向了 NSObject
  • NSObject 类对象的 isa 指向了自己

类的继承关系如下图:


类的继承关系

的继承关系链:ZGTeacher(子类) --> ZGPerson(父类) --> NSObject(根类)-->nil

元类的继承关系链:ZGTeacher(子元类) --> ZGPerson(父元类) --> NSObject(根元类)-->NSObject(根类)--> nil

为什么对象都有isa?

理清了isa走位和继承问题,又来了一个新的疑问:为什么 对象都有isa属性呢?这里就不得不提到两个结构体类型:objc_class& objc_object
我们在终端使用 clang命令:

clang -rewrite-objc main.m -o main.cpp

在生成的.cpp文件中我们注意到NSObject的底层编译是NSObject_IMPL结构体,其中Classisa指针的类型,是由objc_class定义的类型,
objc_class是一个结构体。在iOS中,所有的Class都是以objc_class为模板创建的

struct NSObject_IMPL {
    Class isa;
};


typedef struct objc_class *Class;

我们在objc781的源码中搜索objc_class :发现在文件objc-runtime-new.h

新版objc_class定义

可以发现结构体objc_class是继承objc_object结构体的(在c++中结构体是可以继承的),而我们搜索objc_object {发现位于 objc-privat.h的源码发现了isa的定义!由此就可以解释为什么OC中所有的对象都有一个isa的指针!

objc_object定义

总结:

  • 结构体类型objc_class 继承自objc_object类型,其中objc_object也是一个结构体,且有一个isa属性,所以objc_class也拥有了isa属性
  • mian.cpp底层编译文件中,NSObject中的isa在底层是由Class 定义的,其中class的底层编码来自objc_class类型,所以NSObject也拥有了isa属性
  • NSObject 是一个类,用它初始化一个实例对象objcobjc 满足 objc_object 的特性(即有isa属性),主要是因为isa是由 NSObjectobjc_class继承过来的,而objc_class继承自objc_objectobjc_objectisa属性。所以对象都有一个isaisa表示指向,来自于当前的objc_object
  • objc_object(结构体) 是 当前的 根对象,所有的对象都有这样一个特性objc_object,即拥有isa属性

类的结构体分析

类的结构体分析我们主要分析类的结构体里面到底存储了哪些信息?
因为我们在分析类的结构体的时候需要用到内存偏移的知识,所以我们先补充一下内存偏移.
我们从普通指针对象指针数组指针逐个分析内存的偏移.

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        
        //普通指针
        int a = 10; //变量
        int b = 10;
        NSLog(@"%d -- %p", a, &a);
        NSLog(@"%d -- %p", b, &b);
        
        //对象指针
        ZGPerson *p1 = [ZGPerson alloc];
        ZGPerson *p2 = [ZGPerson alloc];
        NSLog(@"%@ -- %p", p1, &p1);
        NSLog(@"%@ -- %p", p2, &p2);
        
        //数组指针
        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);
        // Setup code that might create autoreleased objects goes here.
    }
    
    return 0;
}

打印结果

打印结果
普通指针 普通指针打印结果
  • a、b都指向10,但是a、b的地址不一样,这是一种拷贝,属于值拷贝,也称为浅拷贝

  • a,b的地址之间相差 4 个字节,这取决于a、b的类型

普通指针内存情况如图:


普通指针

对象指针

对象指针打印结果
  • p1、p2是指针,p1是 指向 [ZGPerson alloc]创建的空间地址,即内存地址,p2 同理

  • &p1、&p2是 指向p1、p2对象指针的地址,这个指针 就是二级指针

对象指针内存情况如图:

对象指针

数组指针

数组指针打印结果
  • &c&c[0]都是取 首地址,即数组名等于首地址
  • &c&c[1]相差4个字节,地址之间相差的字节数,主要取决于存储的数据类型
  • 可以通过 首地址+偏移量取出数组中的其他元素,其中偏移量是数组的下标,内存中首地址实际移动的字节数 等于 偏移量 * 数据类型字节数

数组指针内存情况如图:


数组指针

类结构分析
接下来我们对objc781objc-runtime-new.h文件中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

    ...省略部分代码

通过首地址+内存偏移我们可以打印objc_class结构体中存储的信息.

  • isa属性:继承自objc_object,占8个字节
  • superclass属性:Class类型指针,指针8个字节
  • cache: cache_t结构体类型,结构体的内存大小有结构体本身的属性决定,详情可参考本人的另一篇文章结构体与内存对齐
  • bits:我们得到上面3个属性的大小之后,通过首地址偏移就可以得到bits的内容

现在不能确定大小的就是结构体cache的内存大小,我们进入cache的源码中看一下

struct cache_t {
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
    explicit_atomic<struct bucket_t *> _buckets;
    explicit_atomic<mask_t> _mask;
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
    explicit_atomic<uintptr_t> _maskAndBuckets;
    mask_t _mask_unused;

...一些static属性

#if __LP64__
    uint16_t _flags;
#endif
    uint16_t _occupied;

...一些static属性

由于结构体中static的属性不占内存大小,所以我们只要关注上面的几个属性的内存大小

  • _flags属性:uint16_t类型,uint16_tunsigned short的别称,占2个字节
  • _occupied属性:uint16_t类型,uint16_tunsigned short的别称,占2个字节
  • 下面我们看if-else条件中各属性的大小
    if流程:
    _buckets属性:struct bucket_t *类型的结构体指针类型,占8个字节
    _mask属性:mask_t类型,mask_tunsigned int 的别名,占4字节
    elif流程:
    _maskAndBuckets属性:uintptr_t类型,是一个指针,指针占8个字节
    _mask_unused属性:mask_t类型,mask_tunsigned int的别名,占4字节

总结:cache属性的内存大小:8+4+2+2 = 16字节,所以我们想获取bits属性中的内容我们需要偏移8+8+16字节。

bits属性内容
通过LLDB调试bits内容如下图所示

LLDB调试bits

其中data()获取数据是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

    class_rw_t *data() const {
        return bits.data();
    }
 ...省略部分代码
 

通过LLDB获取方法列表

LLDB获取方法列表
  • class_rw_t中的methods()函数,可以获取到方法列表.
    *list函数获取列表的首地址,打印地址的值后,用get查看到类中的所有方法。通过下图能看到类中定义的say666方法、属性namesettergetter方法,还有一个系统生成的.cxx_destruct方法:

  • 类中还定义了一个类方法+ (void)say888,在这里是看不到的。因为类方法存在元类中。

通过LLDB获取属性列表

LLDB获取属性列表
  • class_rw_t中的properties()函数,可以获取到属性列表.
    *list函数获取列表的首地址,打印地址的值后,用get查看到类中的所有属性。不过却看不到成员属性,调用p $6.get(2)会报错数组越界

成员属性
那么对象的成员属性到底存放在哪里呢?经过探索我们终于发现~

LLDB获取成员属性
  • 原来成员变量存在ro中,通过函数ro()获取,最终我们在roivars属性中发现了我们的成员变量age

至此,大功告成!

总结一下我们这篇文章的内容

  • isa指针指向及继承关系
  • 探索为什么对象都有isa
  • 类的结构体分析
  • 补充知识-内存偏移

相关文章

  • 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/xnnnektx.html