汇编跟踪 Objc alloc 实现 & init

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

而后分享一下 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 文件里开始了。测试

说明:ui

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

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

Notespa

注意请连接真机就行调试,不要用电脑,由于电脑和收回系统架构不一样,汇编代码也是不一样的。3d

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

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

开始

  1. 首先我是找 alloc 这个方法的实现。因此我使用符号断点标记 alloc, 只要通过这个方法的都会停留下来。

  1. 运行,他会停留在 _objc_rootAlloc 这个函数这里. code

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

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

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

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

Objc_mseSend(id self, SEL _cmd)

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

在内存寄存器中,x0 默认是方法调用者, x1方法实现编号. 那么结合上图能够看到上面的 x0 地址,使用 po 命令便可查看对应的值。 咱们能够看到是CTStudent ,正是咱们 alloc的调用者. 若是肯定是咱们关注的对象,那么咱们就须要ge 5. 我把 class_createInstance 加入断点中。

接下来会来到 class_createInstance 的调用流程中。

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

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

那么,说明此时 上的 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 走位图

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 中作了一些初始化操做配置而已。

相关文章
相关标签/搜索