iOS底层原理之类的原理分析一

目录:

1:isa指向与元类的superclass指向分析

书接上文OC底层原理初探之对象的本质(三)alloc探索下继续对isa进行探索。身为iOS开发者都知道对象的isa指向类,那么今天就来分析下isa的走位图。

1.1 isa掩码(ISA_MASK)介绍

上文OC底层原理初探之对象的本质(三)alloc探索下中已经介绍过ISA_MASK及其使用方法,接下来我们的探索需要借助ISA_MASK,现行源码版本一共分为三种架构:

x86_64: define ISA_MASK                     0x00007ffffffffff8ULL // ULL: unsigned long long 无符号长整形
arm64: define ISA_MASK                      0x0000000ffffffff8ULL
arm64(simulators): define ISA_MASK          0x007ffffffffffff8ULL
复制代码

1.2 isa指向分析

代码示例:

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

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        XJPerson *p = [XJPerson alloc];
        NSLog(@"%@",p);
    }
    return 0;
}

********************** lldb调试结果 **********************

(lldb) x/4gx p                                     // 16进制格式化输出对象p的内存信息
0x10044f160: 0x001d800100008365 0x0000000000000000 // 得到首地址的isa
0x10044f170: 0x0000000000000000 0x0000000000000000
(lldb) p/x 0x001d800100008365 & 0x00007ffffffffff8 // 用前文的方式,isa & ISA_MASK
(long) $1 = 0x0000000100008360                     // 得到类的地址信息
(lldb) po 0x0000000100008360                       // po 输出
XJPerson                                           // 得到XJPerson

// 继续探索
(lldb) x/4gx 0x0000000100008360                    // 16进制格式化输出XJPerson类的内存信息
0x100008360: 0x0000000100008338 0x00007fff932d8118
0x100008370: 0x00007fff6bbb6140 0x0000802c00000000
(lldb) p/x 0x0000000100008338 & 0x00007ffffffffff8 // 用首地址 & ISA_MASK
(long) $3 = 0x0000000100008338                     // 得到一个新的地址
(lldb) po 0x0000000100008338                       // po 输出
XJPerson                                           // 还是可以得到XJPerson

复制代码

很显然,两次输出XJPerson的地址不一样,分别为0x00000001000083600x0000000100008338,这里不禁产生了一个疑问,难道类在运行时会类似对象一样创建多份吗?接下来验证一下:

#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import "XJPerson.h"

//MARK: - 分析类对象内存存在个数
void xjTestClassNum(void){
    Class class1 = [XJPerson class];
    Class class2 = [XJPerson alloc].class;
    Class class3 = object_getClass([XJPerson alloc]);
    Class class4 = [XJPerson alloc].class;
    NSLog(@"\n%p-\n%p-\n%p-\n%p",class1,class2,class3,class4);
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        xjTestClassNum();
    }
    return 0;
}

********************** 打印输出结果 **********************

2021-06-19 15:39:55.257537+0800 002-isa分析[5557:148166] 
0x100008360-
0x100008360-
0x100008360-
0x100008360
Program ended with exit code: 0

复制代码

从上面案例可以看出,不管生成多少个对象,类始终只有一个,那么0x0000000100008338这个地址po输出为什么也是XJPerson呢?下面用MachOView读取运行文件看看究竟:

image.png

MachOView搜索class除了能看到_OBJC_CLASS_$_XJPerson外,还能看到_OBJC_METACLASS_$_XJPerson,这就是元类(MetaClass)。我们代码中并没有主动生成元类,说明这是程序在运行时主动帮我们生成的。

到这里已经知道对象的isa->类对象,类对象的isa->元类(MetaClass),继续深入探索:

// 省略一些之前代码,并添加XJTeacher类的实例对象的创建代码,XJTeacher继承自XJPerson

// 实例化XJTeacher类的对象
XJTeacher *t = [XJTeacher alloc];
NSLog(@"%@",t);

********************** lldb调试结果 **********************

// XJPerson lldb 调试

// 省略一些之前XJPerson类lldb调试内容

(lldb) po 0x0000000100008338                          // XJPerson类的元类
XJPerson

(lldb) x/4gx 0x0000000100008338                       // 16进制格式化输出XJPerson元类的内存信息
0x100008338: 0x00007fff932d80f0 0x00007fff932d80f0    // 首地址为isa
0x100008348: 0x0000000100406c50 0x0003e03500000007
(lldb) p/x 0x00007fff932d80f0 & 0x00007ffffffffff8    // isa & ISA_MASK
(long) $5 = 0x00007fff932d80f0                        // 得到新的地址
(lldb) po 0x00007fff932d80f0                          // po 输出
NSObject                                              // 输出为NSObject,那么这是类还是元类呢

// XJTeacher lldb 调试

(lldb) x/4gx t                                        // 16进制格式化输出对象t的内存信息
0x100522330: 0x001d800100008315 0x0000000000000000    // 首地址为isa
0x100522340: 0x0000000000000000 0x0000000000000000
(lldb) p/x 0x001d800100008315 & 0x00007ffffffffff8    // isa & ISA_MASK
(long) $1 = 0x0000000100008310                        // 得到新的地址
(lldb) po 0x0000000100008310                          // po 输出
XJTeacher                                             // 输出为XJTeacher,即类XJTeacher

(lldb) x/4gx 0x0000000100008310                       // 16进制格式化输出类XJTeacher的内存信息
0x100008310: 0x00000001000082e8 0x0000000100008360    // 首地址为类XJTeacher的isa
0x100008320: 0x00007fff6bbb6140 0x0000802c00000000
(lldb) p/x 0x00000001000082e8 & 0x00007ffffffffff8    // isa & ISA_MASK
(long) $3 = 0x00000001000082e8                        // 得到新的地址
(lldb) po 0x00000001000082e8                          // po 输出
XJTeacher                                             // 输出为XJTeacher,即元类XJTeacher

(lldb) x/4gx 0x00000001000082e8                       // 16进制格式化输出元类XJTeacher的内存信息
0x1000082e8: 0x00007fff932d80f0 0x0000000100008338    // 首地址为元类XJTeacher的isa
0x1000082f8: 0x00000001006433a0 0x0003e03500000007
(lldb) p/x 0x00007fff932d80f0 & 0x00007ffffffffff8    // isa & ISA_MASK
(long) $5 = 0x00007fff932d80f0                        
(lldb) po 0x00007fff932d80f0                          
NSObject                                              // 输出为NSObject,那么这是类还是元类呢

// NSObject lldb 调试
// 下面验证一下

(lldb) p/x [NSObject class]                           // 16进制输出NSObject的类的内存信息
(Class) $7 = 0x00007fff932d8118 NSObject              // 得到根类NSObject的地址
(lldb) x/4gx 0x00007fff932d8118                       // 16进制格式化输出根类NSObject的内存信息
0x7fff932d8118: 0x00007fff932d80f0 0x0000000000000000 // 首地址为根类NSObject的isa
0x7fff932d8128: 0x00000001004992d0 0x0001801000000003
(lldb) p/x 0x00007fff932d80f0 & 0x00007ffffffffff8    // isa & ISA_MASK
(long) $8 = 0x00007fff932d80f0                        // 得到新的地址
(lldb) po 0x00007fff932d80f0                          // po 输出
NSObject                                              // 输出为NSObject,根类的元类,即根元类(RootMetaClass)

// 继续验证
(lldb) x/4gx 0x00007fff932d80f0
0x7fff932d80f0: 0x00007fff932d80f0 0x00007fff932d8118
0x7fff932d8100: 0x0000000100643750 0x0005e03100000007
(lldb) p/x 0x00007fff932d80f0 & 0x00007ffffffffff8
(long) $18 = 0x00007fff932d80f0
(lldb) po 0x00007fff932d80f0
NSObject
复制代码

图解:

XJPerson的isa走位图

XJTeacher的isa走位图

NSObject的isa走位图1

NSObject的isa走位图2

结论:

  1. 非根类NSObject类的实例对象的isa指向类(Class),类的isa指向元类(MetaClass),元类的isa指向根元类(RootMetaClass),而不是父类的元类。
  2. 根类NSObject类的实例对象的isa指向根类(RootClass),根类的isa指向根元类(RootMetaClass)。
  3. 根元类的isa还是指向根元类。

1.3 元类superclass指向分析

代码实例:

#pragma mark - 元类的superclass链
void xjSuperclass(void) {
    
    //NSObjcet实例对象
    NSObject *objectInstance = [NSObject alloc];
    //NSObject类
    Class object = object_getClass(objectInstance);
    //NSobject元类
    Class metaClass = object_getClass(object);
    //NSObjct根元类
    Class rootMetaClass = object_getClass(metaClass);
    
    NSLog(@"\n%p NSObject实例对象\n%p NSObject类\n%p NSObject元类\n%p NSObject根元类\n",objectInstance,object,metaClass,rootMetaClass);
    
    // XJPerson的元类
    Class personMetaClass = object_getClass(XJPerson.class);
    NSLog(@"XJPerson的元类 : %@ - %p",personMetaClass, personMetaClass);
    
    // XJPerson元类的父类
    Class personSuperMetaclass = class_getSuperclass(personMetaClass);
    NSLog(@"XJPerson元类的父类 : %@ - %p",personSuperMetaclass, personSuperMetaclass);
    
    // XJTeacher的元类
    Class teacherMetaClass = object_getClass(XJTeacher.class);
    NSLog(@"XJTeacher的元类 : %@ - %p",teacherMetaClass,teacherMetaClass);
    
    // XJTeacher元类的父类
    Class childrenSuperMetaClass = class_getSuperclass(teacherMetaClass);
    NSLog(@"XJTeacher元类的父类 : %@ - %p",childrenSuperMetaClass,childrenSuperMetaClass);
    
    // NSObject类的父类
    Class superObject = class_getSuperclass(NSObject.class);
    NSLog(@"NSObject的父类 : %@ - %p",superObject,superObject);
    
    //NSObject根元类的父类
    Class rootSuperMetaObject = class_getSuperclass(rootMetaClass);
    NSLog(@"NSObject根元类的父类 : %@ - %p",rootSuperMetaObject,rootSuperMetaObject);
}

********************** 打印输出结果 **********************

2021-06-19 23:10:35.443934+0800 002-isa分析[12886:361372] 
0x100644850 NSObject实例对象
0x7fff932d8118 NSObject0x7fff932d80f0 NSObject元类
0x7fff932d80f0 NSObject根元类

2021-06-19 23:10:35.444578+0800 002-isa分析[12886:361372] XJPerson的元类 : XJPerson - 0x100008338
2021-06-19 23:10:35.444650+0800 002-isa分析[12886:361372] XJPerson元类的父类 : NSObject - 0x7fff932d80f0
2021-06-19 23:10:35.444800+0800 002-isa分析[12886:361372] XJTeacher的元类 : XJTeacher - 0x1000082e8
2021-06-19 23:10:35.444838+0800 002-isa分析[12886:361372] XJTeacher元类的父类 : XJPerson - 0x100008338
2021-06-19 23:10:35.444908+0800 002-isa分析[12886:361372] NSObject的父类 : (null) - 0x0
2021-06-19 23:10:35.444942+0800 002-isa分析[12886:361372] NSObject根元类的父类 : NSObject - 0x7fff932d8118

复制代码

结论:

  1. 元类之间也存在继承链,与类一样。
  2. 根元类的父类是根类。
  3. 根类的父类是nil。

图解:

isa流程图.png

2:类的结构分析

本小节将对类的结构进行分析,前面探索过类在底层实现就是objc_class结构体,源码里搜索struct objc_class,会发现在objc4-818.2源码里有三处定义:

2.1 ObjC 1.0的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;
复制代码

2.2 ObjC 2.0的objc_class(基于objc4-818.2

2.2.1 objc-runtime-old.h文件里objc_class的定义

struct objc_class : objc_object {
    Class superclass;
    const char *name;
    uint32_t version;
    uint32_t info;
    uint32_t instance_size;
    struct old_ivar_list *ivars;
    struct old_method_list **methodLists;
    Cache cache;
    struct old_protocol_list *protocols;
    // CLS_EXT only
    const uint8_t *ivar_layout;
    struct old_class_ext *ext;
    
    // 篇幅原因,省略大部分代码
}
复制代码

2.2.2 objc-runtime-new.h文件里objc_class的定义

// 现行版本所使用的,我们的探索也是基于这个定义

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;              // 8字节,隐藏变量isa,继承自objc_object
    Class superclass;          // 8字节
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
    
    // 篇幅原因,省略大部分代码
}
复制代码

图解:

image.png

objc-runtime-new.h文件里objc_class的定义可以看出一共有4个成员变量,分别为isa(继承自objc_object的隐藏变量)、superclasscachebits,其中isasuperclass已经分析过了,这里不在赘述,查看cachebits的注释和数据类型定义,终于在struct class_rw_t里发现了方法、属性和协议的相关信息,接下来我们就对bits进行相关探索、验证。

image.png

2.3 objc_class成员变量bits的分析

已知类名,想要拿到成员变量bits,需要通过内存偏移的方式,下面就先简单介绍下内存偏移。

2.3.1 内存偏移

案例:

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

复制代码
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享