iOS底层原理探索 一 alloc&init探索

iOS底层原理探索篇 主要是围绕底层进行源码分析-LLDB调试-源码断点-汇编调试,让本身之后回顾复习的😀😀设计模式

目录以下:bash

iOS底层原理探索 一 开篇app

iOS底层原理探索 一 alloc&init探索函数

iOS底层原理探索 一 内存对齐&malloc源码分析源码分析

iOS底层原理探索 一 isa原理与对象的本质post

准备条件(以我当前的配置为主)

  • macOS 10.15.2
  • Xcode 11.3
  • objc4-756.2 (若是你想本身配置可编译objc-756.2的源码,能够看看Cooci帅哥的这篇文章)

alloc探索

咱们先来看下面这段代码:ui

TCJPerson *p1 = [TCJPerson alloc];
    TCJPerson *p2 = [p1 init];
    TCJPerson *p3 = [p1 init];
复制代码

看完代码,有一个疑问就是:p一、p二、p3有什么联系呢? 为了弄清此问题,那就看运行结果呗: spa

经过运行结果,能够知道p一、p二、p3 他们所指的内存空间是同样的.也就是有三个不一样指针地址指向同一个内存空间. 那为何会是这样的呢?上面的 allocinit到底作了什么呢? 咱们先来看看 alloc的实现:下面介绍三种方式来查看他的实现.(查看过程需用真机查看,由于模拟器查找的是x86_64环境和arm64是不同的)

三种方式查看alloc的实现

第一种方式:直接下断点.

来到研究对象断点后(26行断点处),接下来按住control键和箭头所指的键会看到以下图所示:
继续以前的操做(按住control键和箭头所指的键)以后会跳转到以下图所示:
到此咱们能够看到 objc_alloclibobjc.A.dylib这个动态库里面.

第二种方式:下断符号断点.

第一步点左下角的**+号按钮以后,在点击Symbolic Breakpoint**设计

第二部添加alloc符号断点:3d

以后过掉断点以后会显示以下:

第三种方式:经过汇编查看.

如何操做以下图箭头所示:

以后会显示下图所示:

这时咱们能够看到在22行有objc_alloc,那在此处打下断点按住control键和图上键头所指的键结果所下:

以后咱们继续以前的操做(按住control键和箭头所指的键)结果以下:

经过这三种方式咱们能够知道objc_alloclibobjc.A.dylib这个动态库里面,那接下来咱们来经过alloc的源码来分析.在这以前,咱们用寄存器来读取一下:那么什么是寄存器呢? 寄存器就是应该存储一些指针的一些东西,由于汇编它就是利用寄存器,用的妥妥的.过掉第一个断点(37行断点),来到alloc断点以下:

其中 x0~x7用于程序调用的参数传递, x0是第一个参数的传递者也是返回的时候返回值的存储地方.所以咱们通常读x0就能够了.以后过掉 alloc断点来到 _objc_rootAlloc断点:
objc_msgSend打上断点,来到断点处:
到此处发现汇编很难跟,一不当心就过了,为此咱们仍是用源码来跟吧.(嘿嘿汇编有点皮,搞不定😊😊)

alloc源码分析

打开可编译的objc756.2源码,经过前面的探究咱们能够看到,在调用alloc以前还调用了objc_alloc,咱们打下断点一步一步去看,图以下:

objc_alloc 方法:

咱们先来到这个断点之处,而后全局搜索objc_alloc,以下图打上断点,以后咱们过掉上图的断点,回来到下面的断点之处,这时咱们看到allocWithZone返回的是false.在下图中我作了详细的解释.

那么为何objc_alloc这个流程只会走一次呢?请看下图

从上图中能够看到符号绑定的操做是在fixupMessageRef这个方法里面实现的.而fixupMessageRef的调用又是在_read_images里面调用的.也就是dyld读取咱们的镜像文件的时候.然而,在咱们读取镜像文件的时候,系统会判断是否须要FIXUP,若是须要的话,咱们就会调用fixupMessageRef,而后在fixupMessageRef内部判断当前的消息sel是不是SEL_alloc,若是是的话就替换其IMP为objc_alloc.其中FIXUP只会走一次,也就是说objc_alloc只会走一次. 以后会继续走callAlloc方法:在这个方法中以下图所示allocWithZone返回false,以后在走alloc方法.

补充另外一种方法说明objc_alloc只会走一次:

  1. 先说说为何会走objc_alloc?由于在LLVM的底层会调用CGF.EmitCallOrInvoke

  2. 在说说objc_alloc只会走一次:

    • 正如前面说的第一次的时候call-objc_alloc->none没有返回对象;经过LLVM源码能够看到:
      若是返回值为none时,
    return CGF.EmitObjCAlloc(Receiver, CGF.ConvertType(ResultType))
    复制代码

    也返回为none,那么就会进行下面的条件判断

    if (Optional<llvm::Value *> SpecializedResult =
    tryGenerateSpecializedMessageSend(CGF, ResultType, Receiver, Args,Sel, Method, isClassMessage))
    复制代码

    而此时并无进入

    return RValue::get(SpecializedResult.getValue())
    复制代码

    可是此时调用了objc_alloc方法,以后会继续走

    return GenerateMessageSend(CGF, Return, ResultType, Sel, Receiver, Args, OID, Method)
    复制代码

    调用alloc方法.

    • 也能够这样说:走的是symbol符号绑定,没有走objc_msgSend
    • 并非咱们调用的,而是系统调用符号的(编译器调用)
alloc方法:

_objc_rootAlloc方法:

callAlloc方法:

此方法内部有一系列的判断条件,其中因为方法canAllocFast()的内部调用了bits.canAllocFast(),其返回值为固定值false,因此能够肯定以后建立对象会走class_createInstance方法

class_createInstance方法:

进入class_createInstance方法,其内部调用了_class_createInstanceFromZone方法,并在其中进行size计算,内存申请,以及isa初始化:

_class_createInstanceFromZone方法:

咱们先来看看对象size的计算,经过方法cls->instanceSize(extraBytes),计算出size,其中64位系统下,对象大小采用8字节对齐,可是实际申请的内存最低为16字节,也就是说系统分配内存按照16字节对齐分配

在这过程当中还有两个核心重点:

  • obj = (id)calloc(1, size)
  • obj->initInstanceIsa(cls, hasCxxDtor)
  • 这两行代码应该直接决定了alloc的做用 咱们来对这两个核心内容分析一下: 建立指针,申请内存
    通过calloc函数建立了一个指针,这个指针是怎么建立的,这个源码在:libmalloc.(具体的分析在OC对象原理(二) 内存对齐探索&malloc源码分析文中有写) 最后根据不一样的条件,使用calloc或者malloc_zone_calloc进行内存申请,而且初始化isa指针,至此size大小的对象obj已经申请完成,而且返回.

init源码分析

进入init方法:

经过源码能够看到,其实 init方法什么事情都没有作。那为何 init会什么都不作呢? 其实这是一种设计模式,本身思考一下,平常开发过程当中,咱们会在什么状况下,进行 init方法的使用。 —— 重写 在重写默认初始化的时候,咱们能够根据本身的需求,进行各类个性化的设置。 工厂设计, 父类没有执行,交给子类去实现

至此,咱们在回到前面的问题?就很好的知道p一、p二、p3他们的内存地址为何同样了吧.

扩展new的底层实现

进入new方法:

经过源码:在new方法里面就是调用alloc的实现(callAlloc)后 进行了init操做,因而可知,[Class new] 彻底等价于 [[Class alloc] init].

总结

  • alloc建立了对象而且申请了一块很多于16字节的内存控件.
  • init其实什么也没作,返回了当前的对象。其做用在于提供一个范式,方便开发者自定义.
  • new其实就是调用alloc的实现(callAlloc)后 进行了init操做.

最后附上alloc流程图一张

相关文章
相关标签/搜索