美文网首页
iOS底层探索alloc

iOS底层探索alloc

作者: 大橘猪猪侠 | 来源:发表于2020-09-05 22:34 被阅读0次

    在我们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] <Person: 0x600000a986f0> - 0x600000a986f0 - 0x7ffee00b3118
    2020-09-05 20:17:48.224434+0800 alloc探索[3152:142267] <Person: 0x600000a986f0> - 0x600000a986f0 - 0x7ffee00b3110
    2020-09-05 20:17:48.224586+0800 alloc探索[3152:142267] <Person: 0x600000a986f0> - 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

          本文链接:https://www.haomeiwen.com/subject/zkhqektx.html