美文网首页
内存布局、isa、结构体值查找、super内部实现

内存布局、isa、结构体值查找、super内部实现

作者: 再好一点点 | 来源:发表于2021-11-17 13:40 被阅读0次

这次具体分析以下代码打印结果:

    id cls = [Person class];
    void *obj = &cls;
    [(__bridge id)obj print];

相关类如下:

Person类
@interface Person : NSObject
@property (copy, nonatomic) NSString *name;
- (void)print;
@end

#import "Person.h"
@implementation Person
- (void)print
{
    NSLog(@"my name is %@", self->_name);
}
@end
ViewController类
#import "ViewController.h"
#import "Person.h"

@interface ViewController ()

@end

@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    id cls = [Person class];
    void *obj = &cls;
    [(__bridge id)obj print];
}
@end

通过上边的代码有以下几个问题
1. print能不能调用成功?为什么?
2. 如果成功print打印什么内容

想要弄清楚以上几个问题需要了解如标题所示的内部原理,只有掌握了这些才能回答好上边的问题。

一. 指针关系

  1. 正常情况下person的isa指向如图:


    正常的personisa指向.png
  2. cls、obj指向如图:


    cls指向.png
  3. 对比如图:


    对比图.png

通过上边几张图对比可以发现通过[(__bridge id)obj print];调用方法和通过Person对象调用方法非常相似。
正常情况下Person对象调用方法通过该对象的isa指针找到对应的类对象,然后开始方法查找,此处cls指向的地址存放的就是Person类对象,将cls的地址赋值给obj就相当于objc就是Person对象,cls就是该对象的isa指针,所以方调用会成功

二. 内存布局

1. super具体作用

这里需要再次深入解析[super viewDidLoad];具体是什么?
使用命令进行转换xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc ViewController.m -o ViewController.cpp
这样获取到的代码只是个大概实现,具体实现还需要根据LLVM具体版本来定,但是这个实现几乎就是运行时的实现。
[super viewDidLoad];转换后如下:

   ((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("ViewController"))}, sel_registerName("viewDidLoad"));
    
    删除转义代码
    objc_msgSendSuper)({
        self, class_getSuperclass(objc_getClass("ViewController"))},
        sel_registerName("viewDidLoad")
                       );

可以看到第一个参数是一个结构体如下:

{
        self, 
        class_getSuperclass(objc_getClass("ViewController"))
}

该结构体第一个参数是一个OC对象且该对象只有一个isa指针所以占用8个字节,至于第二个参数是ViewController还是它的父类需要使用运行时或者直接查看中间代码来确定。
可以使用以下命令行指令生成中间代码
clang -emit-llvm -S main.m
不管是使用运行时查看Debug Workflow还是使用生成中间件的方式都可以看到实际使用的不是objc_msgSendSuper而是objc_msgSendSuper2。然后通过在objc源码里边查找objc_msgSendSuper2可以在objc-msg-arm64中看到如下代码:

        // no _objc_msgLookupSuper

    ENTRY _objc_msgSendSuper2
    UNWIND _objc_msgSendSuper2, NoFrame
    MESSENGER_START

    ldp x0, x16, [x0]       // x0 = real receiver, x16 = class
    ldr x16, [x16, #SUPERCLASS] // x16 = class->superclass
    CacheLookup NORMAL

    END_ENTRY _objc_msgSendSuper2

这段代码可以看出objc_msgSendSuper2的两个参数分别为消息接收者和该接收者对应的类对象,然后通过class->superclass拿到父类的类对象

2. 局部变量内存布局

- (void)viewDidLoad方法内的几个变量内存分布如下:

WeChat2e8cae82d9285a4d0b091c54445d8938.png

由于是在函数内部属于局部变量,所以内存分配在栈空间,栈空间从高地址向低地址分配,所以首先分配[super viewDidLoad];所占空间,由于[super viewDidLoad];在运行时会转换为结构体,所以以结构体的形式分配空间,第一个参数self在低地址,然后[ViewController class]在高地址,如上图所以,再然后分配cls地址,接着分配obj地址,整个内存分布图如上图所示。

在Person内部,内存分布为首先是isa占用8个字节,然后是name属性占用8个字节。isa在低地址,name紧挨着isa在高地址。
结合最上边的几张图,所以在上图中cls就相当于Person对象中的isaself就相当于name属性,所以打印结果为*my name is <ViewController: 0x7fd0aa505030>

如果修改成这样:

#import "ViewController.h"
#import "Person.h"

@interface ViewController ()

@end

@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];

    NSString *test = @"aaaaa";
    id cls = [Person class];
    void *obj = &cls;
    [(__bridge id)obj print];
}
@end

内存分布图为:


新增test.png

打印结果为my name is aaaaa

以上两种情况都可以成功打印是因为selftest都是8个字节,如果把NSString *test = @"aaaaa";换成int a = 1;就会出错,因为在读取内存的时候实际是按照name8个字节读取的,读取的时候int只有4个字节,剩余的4个字节是其他的部分,会导致取值失败,所以崩溃。

相关文章

  • 内存布局、isa、结构体值查找、super内部实现

    这次具体分析以下代码打印结果: 相关类如下: Person类 ViewController类 通过上边的代码有以下...

  • Runtime

    对象的本质是什么?答:结构体,内部存放着isa指针和成员变量的值。

  • Swift探索( 十二): Array源码分析

    一:Array 的内存布局 在 Swift 中 Array 其实是用结构体实现的,所以 Array 是值类型。 ...

  • 元类(Meta Class)

    struct objc_object结构体实例它的isa指针指向类对象,类对象的isa指针指向了元类,super_...

  • iOS内存管理

    内存结果 内存结构 内存布局 retain 实现 release 实现

  • 对isa、IMP、SEL理解

    ISA 每一个类都会有isa指针,该指针指向类的结构体,如在底层objc_msgSent() 就是通过isa来查找...

  • 问题:KVC的底层实现原理

    KVC主要通过isa来实现其内部查找定位的。默认的实现方法由NSOject提供isa指针, 如其名称所指,(就是i...

  • swift之内存布局

    struct和tuple内存布局 结构体和元组当前共享相同的布局算法,在编译器实现中称为“通用”布局算法。算法如下...

  • isa_t

    代替 isa 指针的是结构体 isa_t 当实例方法被调用时,它要通过自己持有的isa 来查找对应的类,然后在ob...

  • iOS runtime 方法查找讲解原理,[self class

    iOS runtime 方法查找讲解原理,[self class],[super class] isa 指针: 1...

网友评论

      本文标题:内存布局、isa、结构体值查找、super内部实现

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