iOS底层探索alloc

在我们iOS开发且使用oc语言开发中,我们创建对象的既可以使用new,也可以使用alloc和init;但是我们常用的一般都是alloc和init,在我们使用这个创建对象时,我们是否会有疑问?alloc和init做了什么事情?接下来我们来探索一下alloc的流程分析。

示例代码:

    Person *p1 = [Person alloc];
    Person *p2 = [p1 init];
    Person *p3 = [p1 init];
    
    NSLog(@"%@ - %p - %p",p1,p1,&p1);
    NSLog(@"%@ - %p - %p",p2,p2,&p2);
    NSLog(@"%@ - %p - %p",p3,p3,&p3);

在上面的代码当中,我们创建了一个person类对象,其中p1只进行了alloc,p2,p3在p1的基础上进行了init。

在我们执行代码之后,得到下面的结果:

2020-09-05 20:17:48.224166+0800 alloc探索[3152:142267]  - 0x600000a986f0 - 0x7ffee00b3118
2020-09-05 20:17:48.224434+0800 alloc探索[3152:142267]  - 0x600000a986f0 - 0x7ffee00b3110
2020-09-05 20:17:48.224586+0800 alloc探索[3152:142267]  - 0x600000a986f0 - 0x7ffee00b3108

从打印的结果可以了解到,p1,p2,p3都为同一个对象,他们所在的内存空间也是一样的,但是他们的地址是连续的且都占8bit。

也就可以得出,三个对象所指向的存储空间都为同一个,在这个存储空间中,三个对象以堆栈的方式连续占用8bit空间。

得到了上面的结果之后,我们还是没能知道alloc和init到底做了什么,我尝试的去点击alloc方法,却始终无法查看他做了什么。

那么我们如何查看alloc的源码实现呢?

下面有三种方式可以查看alloc源码在哪个动态库中:
第一种,下断点:
首先在p1处下断点,然后执行,执行之后,按住control和下图的图标


15993094186011.png

在点击几下之后,跳转到了如下图所示之后,在点击一下, 在左上角就可以看到alloc所在的动态库在libobjc.A.dylib中(注意:这个动态库的查看需要在真机上执行才能查看,由于我身边暂时没有真机,无法用图片表示)


15993094958364.png

第二种:在xcode左下角的+号出点击symbolic Breakpoint下断点,输入alloc,然后执行,断点卡在p1处,点击下一步,就会跳转到下图所示的界面处,就可以看到alloc所在的动态库是在libobjc.A.dylib中,这一步可以不用在真机环境下就可以查看到


15993102676667.png

第三种:通过汇编的方式
首先在p1处下断电,然后运行,接下来点击下图所示的选项。


15993106134251.png

再点击完成之后,就可以看到下图的汇编代码,例如objc_alloc、objc_msgSend等方法。


截屏2020-09-05 下午8.57.27.png

然后再按第一种方法的control+向下的箭头,同样的出现了像第一种方法的界面,再点击一下,就可以在左上角看到alloc所在的动态库(同样的是在真机环境下)。


15993107757876.png

在得到alloc所在的动态库之后,我们就可以在苹果官网macOS 10.15.6 Source去找到我们所需要的代码,我这边的版本是objc4-781最新源码。

如何让这个最新的源码能够执行,请参考文章

接下来,我们就可以在代码中查看我们需要的代码。

搜索alloc {,就会得到下图所示的样子:


15993118295232.png

当我们定位到alloc方法后,我们如何去查看源代码呢?

我们首先通过点击alloc中的方法,一个一个点进去:
_objc_rootAlloc,callAlloc.
当我们点击到callAlloc方法后,就存在if判断了,那么我们如何知道程序执行哪一步呢?

我们回到之前的代码,在代码中添加三个断点:_objc_rootAlloc,callAlloc,_objc_rootAllocWithZone。
在程序执行时,先将这三个断点取消掉(防止其他alloc对p1对象的创建进行干扰),在程序在p1处停下后,在选中三个断点,一步一步执行,最后程序执行的过程为:
1、


15993135816380.png

2、


15993136025796.png

3、


15993136137532.png

那么我们会发现,callAlloc方法不执行吗?

那当然不是,下一步我们来介绍一下编译器优化;
我们既然知道程序最后的执行方法为_objc_rootAllocWithZone,那么我们点击这个方法,再点击_class_createInstanceFromZone方法

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;
    // 1:要开辟多少内存
    size = cls->instanceSize(extraBytes);
    if (outAllocatedSize) *outAllocatedSize = size;

    id obj;
    if (zone) {
        obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
    } else {
        // 2;怎么去申请内存
        obj = (id)calloc(1, size);
    }
    if (slowpath(!obj)) {
        if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {
            return _objc_callBadAllocHandler(cls);
        }
        return nil;
    }

    // 3: ?
    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);
}

最后得到三个重要的方法:
1、

要开辟多少内存空间
cls->instanceSize

2、

怎么申请内存
calloc

3、

obj->initInstanceIsa

那么这三个方法分别做了什么呢?为什么这么重要呢?

首先拿到我们配置好的objc4-781源码,创建一个person类,在main函数中创建一个person类,在p1中打上断点,然后执行程序;我们一步一步点击之alloc的方法,点击到_class_createInstanceFromZone中,在cls->instanceSize打上断点,然后执行下一步,我们可以清楚的看到程序跳转到了size方法处,然后我们点击instanceSize方法,里面有一个fastpath的if判断,在此处打断点,继续下一步,程序依然会跳转到if处,我们点击fastInstanceSize方法,里面依然有一个if判断:

 size_t fastInstanceSize(size_t extra) const
    {
        ASSERT(hasFastInstanceSize(extra));

        if (__builtin_constant_p(extra) && extra == 0) {
            return _flags & FAST_CACHE_ALLOC_MASK16;
        } else {
            size_t size = _flags & FAST_CACHE_ALLOC_MASK;
            // remove the FAST_CACHE_ALLOC_DELTA16 that was added
            // by setFastInstanceSize
            return align16(size + extra - FAST_CACHE_ALLOC_DELTA16);
        }
    }

接下来的流程分析我也就不再继续解释了,就直接写出结果:
首先一个重要的结果就是align16方法的作用:

static inline size_t align16(size_t x) {
return (x + size_t(15)) & ~size_t(15);
}

其作用是16位字节对齐(苹果早期的是8字节对齐,现在是16),16字节的好处很多,例如:防止一些野指针和返回错误,给程序预留空间,保持安全性等。

然后就是alloc的流程:
alloc->_objc_rootAlloc_callAlloc->_objc_rootAllocWithZone->_class_createInstanceFromZone->(cls->instanceSize(计算出需要的内存空间大小),calloc(向系统申请开启内存,返回地址指针),obj->initInstanceIsa(关联到相应的类))

由此得出,alloc是开辟内存空间,那么init做什么事情呢?

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

看上面的代码,他返回的是自己.

new源代码:

+ (id)new {
    return [callAlloc(self, false) init];
}

new的源代码其实就是将alloc和init结合起来,但是,使用new方法我们不能够自定义初始化,而init可以自定义;因此,我经常使用alloc来创建对象。

你可能感兴趣的:(iOS底层探索alloc)