美文网首页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