iOS-底层分析之alloc

379 阅读5分钟

作为一个iOS开发者,当我们创建对象的时候我们都会使用alloc或者new来创建,但是我们调用类的alloc或者new方法的时候具体流程是怎样的呢?  

在开始之前,我们需要将objc的源码配置到我们的项目中,具体怎么配置,请看这篇文章如何在项目中配置objc源码

另外本篇文章使用的是objc4-781源码,如果后面的版本中苹果更新的源码,可能部分流程会不适用,但都大同小异

 如何调试objc源码 

在开发中我们经常遇到系统库的相关方法我们不能跟进去,我们也就无法知道这个方法内部具体是如何实现,这里我们介绍三种方式去调试系统方法 

  • 添加符号断点,直接进入到方法 
  • 按住control,step into 
  • 通过汇编跟进 

这三种方式都只能找到你要调试的方法在源码的什么位置,具体要查看源码如何实现,我们还是要下载objc源码 

 符号断点 

  • 在这里添加符号断点

  • 我们输入alloc,表示对所有的alloc方法添加断点

  • 然后我们在需要调试的地方添加一个普通断点 

注意:在断点进入到我们要调试的位置前,我们要关掉符号断点,因为alloc的调用太多了,很多系统库内部也在调用alloc方法创建对象

 我们可以看到当代码执行到 

WPerson *p1 = [WPerson alloc]; 

的时候,alloc方法被调用 

并且我们知道了alloc方法存在于libobjc.A.dylib系统库中,这时我们只需要去objc源码的对应位置查找即可 

按住control,step into 

还是在WPerson *p1 = [WPerson alloc];位置打一个普通断点,当代码执行到这里的时候,按住control,然后点击单步进入

进入后,我们可以看到alloc方法调用了objc_alloc方法

再次control,step into我们还是看到objc_alloc存在于libobjc.A.dylib系统库中

通过汇编跟进 

打开全局的汇编调试开关

打开开关以后断点位置的代码会以汇编的形式呈现,如下图 

这个时候我们再control,step into,就能找到源码位置 以上是关于怎么去调试查找系统方法源码的方式,接下来的篇幅会介绍alloc的调用流程  

alloc方法的调用流程

 我们使用配置好源码的工程调试,可以直接进入到系统源码内部,通过alloc我们以此进入,我们可以知道调用栈为:

 alloc -> _objc_rootAlloc -> callAlloc -> _objc_rootAllocWithZone -> _class_createInstanceFromZone 

主要的功能调用都在_class_createInstanceFromZone方法中完成,所以我们重点分析这个方法 

_class_createInstanceFromZone方法的源码如下: 

/***********************************************************************
* class_createInstance
* fixme
* Locking: none
*
* Note: this function has been carefully written so that the fastpath
* takes no branch.
**********************************************************************/
static ALWAYS_INLINE id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone,
                              int construct_flags = OBJECT_CONSTRUCT_NONE,
                              bool cxxConstruct = true,
                              size_t *outAllocatedSize = nil)
{
    ASSERT(cls->isRealized());

    // Read class's info bits all at once for performance
    bool hasCxxCtor = cxxConstruct && cls->hasCxxCtor();
    bool hasCxxDtor = cls->hasCxxDtor();
    bool fast = cls->canAllocNonpointer();
    size_t size;

    size = cls->instanceSize(extraBytes);
    if (outAllocatedSize) *outAllocatedSize = size;

    id obj;
    if (zone) {
        obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
    } else {
        // alloc 开辟内存的地方
        obj = (id)calloc(1, size);
    }
    if (slowpath(!obj)) {
        if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {
            return _objc_callBadAllocHandler(cls);
        }
        return nil;
    }

    if (!zone && fast) {
        obj->initInstanceIsa(cls, hasCxxDtor);
    } else {
        // Use raw pointer isa on the assumption that they might be
        // doing something weird with the zone or RR.
        obj->initIsa(cls);
    }

    if (fastpath(!hasCxxCtor)) {
        return obj;
    }

    construct_flags |= OBJECT_CONSTRUCT_FREE_ONFAILURE;
    return object_cxxConstructFromClass(obj, cls, construct_flags);
}

其中对象的创建主要有以下三个方法来完成 

  • size = cls->instanceSize(extraBytes);
  • obj = (id)calloc(1, size);
  • obj->initInstanceIsa(cls, hasCxxDtor);

instanceSize 

这个方法用来计算当前对象需要占用对少内存空间,源码如下:

通过调试发现代码最终会走到 fastInstanceSize方法,我们再进入到fastInstanceSize 

通过调试发现接下来代码会走向align16方法,然而align16只有一句代码:

这个方法到底做了什么呢? 其实这个方法是一个字节对齐的算法,最新的iOS系统都采用的16字节对齐的方式分配内存,具体如何进行字节对齐呢?以8字节为例 

 | 8+15 | 0000 0000 0001 0111 | 

|------|---------------------| 

| 15取反 | 1111 1111 1111 0000 | 

 再对8+15 和 15取反的结果按位与,得到结果

 | 相与 | 0000 0000 0001 0000 |

 |----|---------------------| 

 得到的结果为16,其实这个算法的最终目的就是将低四位进行抹零处理,得到的结果都是16的倍数 我们知道对于一个没有任何属性的类,因为每个类都有一个isa指针,所以都会占用8个字节,由于进行了字节对齐,这个类最终会被分配16字节内存 

calloc 

这个方法是c语言方法,用来开辟内存空间,通过instanceSize方法计算出了对象需要占用的内存大小,需要将大小size传入到calloc中 

initInstanceIsa

 通过调用calloc已经开辟了一段内存空间,但是仅仅是在内存中分配了一段内存空间供程序使用,并没有和我们的对象关联,initInstanceIsa方法就是将内存空间和对象进行关联 

总结下来,_class_createInstanceFromZone的调用流程为:

init源码 

我们再看看init方法的源码实现,我们发现在init方法内部,是直接返回了当前的self对象

实际上init方法只是一个构造方法,用于我再开发的时候自定义初始化操作,init方法并没有对对象做其他修改和设置

new

创建对象 有时候我们创建对象的时候是直接调用new来创建,那这种创建方式和alloc有什么不一样呢? 通过调试源码我们发现,new实际上还是调用callAlloc方法来实现对象的创建 

这和alloc的方式并没有什么不同,但是还是建议在开发中使用alloc的方式来创建,因为大多数时候我们在创建的对象的时候都有对对象进行一些初始化的操作,如果使用new来创建,默认调用的init来初始化,无法自定义初始化