美文网首页iOS Dev
汇编跟踪 Objc alloc 实现 & init

汇编跟踪 Objc alloc 实现 & init

作者: Kare | 来源:发表于2019-05-17 23:13 被阅读54次

今天看了一些关于 Objc 相关的知识,也是一些基础部分的知识。使用汇编来查看 alloc 的实现过程。

然后分享一下 init 到底干了啥。

下面就一起看看具体流程。

我的测试代码如下

#import <UIKit/UIKit.h>
#import "AppDelegate.h"
#import "CTStudent.h"
#import <objc/runtime.h>

int main(int argc, char * argv[]) {
    
    CTStudent *st = [CTStudent alloc];
    NSLog(@"size  %lu", class_getInstanceSize([st class]));
    
    return 0;
}

为了不加载过多的变量及方法,我直接在Main 文件里开始了。

说明:

  1. CTStudent 这个类里面啥都没写。
  2. 使用 alloc 初始化,然后获取 st 的大小。

那么接下来就具体看看使用汇编来看看具体流程。

Note

注意请链接真机就行调试,不要用电脑,因为电脑和收回系统架构不同,汇编代码也是不同的。

在此之前介绍几个接下来要用的汇编命令

  1. bl 或者 b ---> 意思是跳转到指定的函数中去
  2. ret ---> 意思是 方法走到这里就返回了,不在往下面走了
    具体的可以参考网上尽详的资料。

开始

  1. 首先我是找 alloc 这个方法的实现。所以我使用符号断点标记 alloc, 只要经过这个方法的都会停留下来。
image
  1. 运行,他会停留在 _objc_rootAlloc 这个函数这里.

    image
  2. 那么我继续使用断点符号把 _objc_rootAlloc 也就行了标记.

image
  1. 运行发现他会走到一个class_createInstance 的函数调用中去。
image

Note:如果汇编没有走到 class_createInstance 这一步之前,就不用启用 class_createInstance 符号断点,因为系统中的很多函数初始化也会走这个方法,所以我就在这个函数之前打了一个断点,如果真的走到断点符号前面的这个断点,我就会使用 register read 来读取当前内存中的值。那么我为什么会在ret 设置断点了,是因为alloc 调用完了会有返回值。

image
如上图所示。
大家想必都熟悉一个 Runtime 的函数, Objc_mseSend 函数。
Objc_mseSend 默认会有两个值。

Objc_mseSend(id self, SEL _cmd)

* 第一个方法的调用者
* 第二个参数是方法编号

在内存寄存器中,x0 默认是方法调用者, x1方法实现编号.
那么结合上图可以看到上面的 x0 地址,使用 po 命令即可查看对应的值。
我们可以看到是CTStudent ,正是我们 alloc的调用者.
如果确定是我们关注的对象,那么我们就需要ge

  1. 我把 class_createInstance 加入断点中。
image
接下来会来到 class_createInstance 的调用流程中。 image

经过一系列的走位,最终被 return 出去了。


image

然后继续,被 return 到了上一次。此时控制到输出了

image

那么,说明此时 上的 st已经有大小了,大小位:8 字节。那么为啥我什么都没写却是 8 了,因为每个类中都有一个 isa, 是 8 字节大小。

摘自 Objc4-750 源码

inline Class 
objc_object::ISA() 
{
    assert(!isTaggedPointer()); 
#if SUPPORT_INDEXED_ISA
    if (isa.nonpointer) {
        uintptr_t slot = isa.indexcls;
        return classForIndex((unsigned)slot);
    }
    return (Class)isa.bits;
#else
    return (Class)(isa.bits & ISA_MASK);
#endif
}

union isa_t {
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    Class cls;
    uintptr_t bits;
#if defined(ISA_BITFIELD)
    struct {
        ISA_BITFIELD;  // defined in isa.h
    };
#endif
};

可以看出 isa 是一个 uintptr_t 类型。

那么根据汇编来看 alloc 的流程为:

alloc -> _objc_rootAlloc -> class_createInstance. 当然这里面肯定省略了很多步骤,但是使用汇编来查看代码的运行步骤是很有趣的。不妨动手试试看。当然详细的过程,我会以解析源码的过程来阐述明白。

附带今天跟踪 源码时的一张流程图。


alloc 走位图.jpg

init 走位

init 代码实现部分

- (id)init {
    return _objc_rootInit(self);
}

id _objc_rootInit(id obj)
{
    return obj;
}

其实就是返回了自己。

那么为啥要这么做了?
其实就是让你在 init 中做一些初始化配置等操作。

想想,我们平时开发都在 init 中干了啥了。

@interface Student : NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSMapTable *mapTable;
- (void)save;
- (void)log;
@end

@implementation Student
- (instancetype)init
{
    self = [super init];
    if (self) {
        self.name = @"CT_Kare";
        self.mapTable = [[NSMapTable alloc] init];
    }
    return self;
}

- (void)save {
    [self.mapTable setObject:self.name forKey:@"name"];
}

- (void)log {
    NSLog(@"self.mapTable %@", self.mapTable);
}
@end

那么,其实也是做了一些初始化操作。

总结:所以在初始化一个对象时,alloc 做了初始化准备,比如开辟对象空间,init Isa,等操作。init 其实就是返回了初始化对象本身,然后在 init 中做了一些初始化操作配置而已。

相关文章

网友评论

    本文标题:汇编跟踪 Objc alloc 实现 & init

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