iOS底层原理-003 类的结构分析

准备工作

首先定义两个类

  • 继承自NSObject的GLPerson
@interface GLPerson : NSObject
{
    NSString *happy;
}
@property (nonatomic, copy) NSString *cjl_name;
- (void)sayHello;
+ (void)sayBye;
@end

@implementation GLPerson
- (void)sayHello {
    
}
+ (void)sayBye {
    
}
@end
  • 继承自GLPerson的GLTeacher
@interface GLTeacher : GLPerson
@end
@implementation GLTeacher
@end
  • 创建对象
int main(int argc, const char * argv[]) {
    @autoreleasepool {
         GLPerson *person = [GLPerson alloc];
         GLTeacher *teacher = [GLTeacher alloc];
         NSLog(@"\n%@\n%@",person,teacher);
        
    }
    return 0;
}

元类

  • 进入lldb调试
    lldb.png

    我们会发现:po 0x001d800100002485 & 0x00007ffffffffff8ULLpo 0x0000000100002458 & 0x00007ffffffffff8ULL的结果都是GLPerson,但是 地址又不一样。那是什么原因呢。
  • 第一个打印$2是person的isa指针地址 与上 0x00007ffffffffff8ULL的结果是GLPerson类
  • 第二个打印的$6是GLPerson的isa指针地址是GLPerson,那这个类是类的类 ,我们称之为元类.

什么是元类

  • 我们都知道 对象的isa 是指向类,类的其实也是一个对象,可以称为类对象,其isa的位域指向苹果定义的元类

  • 元类是系统给的,其定义和创建都是由编译器完成,在这个过程中,类的归属来自于元类

  • 元类是类对象 的类,每个类都有一个独一无二的元类用来存储 类方法的相关信息

  • 元类本身是没有名称的,由于与类相关联,所以使用了同类名一样的名称。

直接上图

isa指向

从图中可以得出
1.person的isa指向GLPerson类
2.GLPerson的isa指向GLPerson元类
3.GLPerson元类的isa指向NSObjct根元类
4.NSObjct根元类的isa指向自己(地址相同)

可以看出最后的isa指向根元类,那这里的NSObjct和我们开发中用到的NSObjct类是同一个类么,我们两种方法验证一下
通过lldb验证


lldb验证

代码验证
用三种不同的方法获取类

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

输出结果如下


代码验证

从图中可以看出,NSObject类的元类也是NSObject根元类,NSObject根元类的元类也是NSObject根元类。所以可以得出一个结论:内存中只存在存在一份根元类NSObject,根元类的元类是根元类自己。
我们通过不同方式获取的NSObject类对象,他们的地址相同,所以NSObject在内存中只有一份。同理继承于NSObject的类对象在内存中只存在一份。

isa走位图&继承关系图

isa走位&继承关系

objc_class & objc_objectisa

走位我们理清楚了,又来了一个新的问题:为什么没有对象都有isa属性呢?
不提到两个结构体类型:objc_class & objc_object
我们之前提及NSObject的底层编译是NSObject_IMPL结构体

struct NSObject_IMPL {
    Class isa;
};
typedef struct objc_class *Class;

在objc4源码中搜索objc_class的定义,源码中对其的定义有两个版本

  • 旧版 位于 runtime.h中,已经被废除,代码如下
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; // 表示被废除
  • 新版在objc-runtime-new.h,这个是objc4-781最新优化的,我们后面的类的结构分析也是基于新版来分析的。
struct objc_class : objc_object {
    // Class ISA;     // 8
    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() const {
        return bits.data();
    }
    void setData(class_rw_t *newData) {
        bits.setData(newData);
    }
//太长了 省略一部分
    unsigned classArrayIndex() {
        return bits.classArrayIndex();
    }
};

从新版的定义中,可以看到 objc_class 结构体类型是继承自 objc_object的。objc_object是这么定义的:

struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};

objc_class

  • 可以看出objc_object结构体有一个isa属性,所以继承于objc_object的对象有isa。
  • objc_class结构体继承于objc_object,所以继承于objc_class的类有isa

总结

所有的对象都继承于obje_object。所有的类都继承于objc_class。objc_继承于objc_class。所以说所有的对象,类,元类都继承于objc_object。objc_object有isa属性,所以对象,类,元类都有isa属性。(万物皆对象)

类信息的内容

准备工作:

定义一个LGPerson类,添加以下属性和方法

@property (nonatomic,strong) NSString *name;
@property (nonatomic,strong) NSString *nickName;
@property (nonatomic) int hobby; 
- (void)sayHello;
+ (void)sayBye;

我们要怎么能知道类的类内容呢?那现在就要打开objc_class的源码。

来自于objc-runtime-new

可以看到有四个属性:
isa属性:继承自objc_object,占8字节。
superclass 属性:Class类型的指针,占8字节。
cache属性:cache_t类型我们并不知道它占多少个字节,需要我们进入内部看。
![struct cache_t]](https://upload-images.jianshu.io/upload_images/1705709-2d4f9eb891b8c468.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

可以看到是一个结构体类型,那就是8字节了。
其实不对,只有结构体指针是8字节,而这时并不是指针,结构体的大小要看内部成员的总和。所以不是8字节。
注意图中,static在全局区,所以我们这里不需要计算。
所以就剩下了以下属性:

#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
    explicit_atomic _buckets; 
    explicit_atomic _mask; 
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
    explicit_atomic _maskAndBuckets;
    mask_t _mask_unused;
#if __LP64__
    uint16_t _flags; 
#endif
    uint16_t _occupied; 
第一种情况

_buckets:bucket 是一个struct类型的指针,所以占8字节。

///类型转换
typedef uint32_t mask_t
typedef unsigned int uint32_t;
typedef unsigned short uint16_t;

_mask:是mask_t类型,并不是常用类型,但是可以看到是其实是int类型所以占用4字节。
flags:uint16是short的转换。占2字节。
_occupied:也占2字节。
占用内存为:8+4+2+2 = 16字节。

第二种情况

_maskAndBuckets:uintptr_t>类型,占用8字节
占用内存为:8+4+2+2 = 16字节。

所以cache占用16字节内存
前三个属性共占用内存32字节
根据内存偏移,获取bits需要将累的首地址平移(前三位的内存和)32位就可以得到

bits:

通过lldb获取属性: property_list


读取bits存储的属性

通过lldb获取方法: methods_list


通过lldb获取方法

总结

  • 实例对象是类的对象,类是元类的对象,元类是根源类的对象,根源类是根根元类(自己)的对象
  • 成员变量存储存储在类的bits属性中,通过bits --> data() -->ro() --> ivars获取成员变量列表,除了包括成员变量,还包括属性定义的成员变量
  • 属性,存储在bits属性中,通过bits --> data() --> properties() --> list获取属性列表,其中只包含属性
  • 类的实例方法存储在类的bits属性中,通过bits --> methods() --> list获取实例方法列表,例如GLPerson类的实例方法sayHello 就存储在 GLPerson类的bits属性中,类中的方法列表除了包括实例方法,还包括属性的set方法 和 get方法
  • 类的类方法存储在元类的bits属性中,通过元类bits --> methods() --> list获取类方法列表,例如GLPerson中的类方法sayBye 就存储在GLPerson类的元类(名称也是GLPerson)的bits属性中

你可能感兴趣的:(iOS底层原理-003 类的结构分析)