iOS底层原理 01:alloc&init探索 01

535 阅读3分钟

写了多年的OC代码,alloc&init是老朋友了,我们总是在创建对象的时候总是用到,但却没有探究过其背后到底做了什么?这篇博客带你深入了解alloc&init。

1.对象alloc和init的后内存地址

image.png

可以看到:alloc会创建一块内存控件,init不会创建内存空间也不会改变内存空间地址,p1 p2 p3三个指针地址为连续的,指向了同一块内存空间。

2.alloc方法的执行流程

备注:当前运行在模拟器,真机cpu指令集不一样

1.下好断点,运行程序

image.png

2.运行程序到断点处,添加符号断点

请注意执行到此处断点时在添加符号断点,这是给所有类的alloc方法都加上了断点。 image.png

image.png

image.png

3.点击 Step over

image.png

4.点击会进入libobjc.A.dylib

这里可以看到这里调用了父类(NSObject)的方法 +[NSObject alloc] , 还可以看到父类的方法jmp到了_objc_rootAlloc

image.png

5.继续添加符号断点,追踪_objc_rootAlloc

image.png

6.点击 Step over,查看 _objc_rootAlloc

这里可以看到 _objc_rootAlloc 有调用到 _objc_rootAllocWithZone,继续添加符号断点,继续追查

image.png

7.继续添加符号断点,查看 _objc_rootAllocWithZone

运行断点进入到 ”_objc_rootAllocWithZone“ 可以看到在22有retq 在这里return了,回到_objc_rootAlloc,然后执行objc_msgSend

image.png

到此alloc的基本走的流程已经清楚了,alloc->_objc_rootAlloc->_objc_rootAllocWithZone->objc_msgSend,至于具体流程是如何,我们来查看源代码

3.alloc在源码中如何执行

苹果源码下载地址:

Apple Open Source

Source Browser

这里用的是objc4-818.2版本,具体的源码如何配置这里不做过多的说明。

1. 进入到alloc方法可以看到首先调用的就是上面我们在libobjc.A.dylib看到的

_objc_rootAlloc 方法 image.png

2. 然而进入_objc_rootAlloc看到的调用的是callAlloc,进入callAlloc里面才发现里面有调用_objc_rootAllocWithZone(很显然callAlloc没有变成汇编的jmp调用而是直接优化成汇编代码了),接下来看一下逻辑!cls->ISA()->hasCustomAWZ()

image.png

image.png

3. !cls->ISA()->hasCustomAWZ(),这里是去cache中查看是否有缓存了,如果有会直接执行_objc_rootAllocWithZone,如果没有会通过objc_msgSend调用alloc方法,然后又是_objc_rootAlloc->callAlloc,cache中存在缓存时就会调用_objc_rootAllocWithZone

image.png

image.png

4. 进入到_objc_rootAllocWithZone,里面调用到_class_createInstanceFromZone

image.png

5. 进入_class_createInstanceFromZone,发现alloc的具体操作都在这里,接下来就是具体分析_class_createInstanceFromZone进行了什么操作

_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 {
        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);
}

6. 首先是cls->instanceSize()计算需对象要开辟的内存空间,接下来是calloc()开辟内存空间,然后obj->initInstanceIsa()

image.png

image.png

image.png

7. 到这里感觉alloc的执行流程已经明了了,但是还有一个疑问,在符号断点时明明是执行的objc_alloc,objc_alloc到底有没有执行到,我们还要深究一下,我们给alloc、objc_alloc、callAlloc、_objc_rootAlloc、_objc_rootAllocWithZone、_class_createInstanceFromZone都机上一个printf打印,然后执行试一试。

image.png

image.png

这里发现NSObject执行流程objc_alloc->callAlloc->_objc_rootAllocWithZone->_class_createInstanceFromZone。我们自己定义的类的执行顺序为objc_alloc->callAlloc->alloc->objc_rootAlloc->callAlloc->_objc_rootAllocWithZone->_class_createInstanceFromZone

8. 总结 alloc流程

NSObject执行流程objc_alloc->callAlloc->_objc_rootAllocWithZone->_class_createInstanceFromZone。

我们自己定义的类的执行顺序为objc_alloc->callAlloc->alloc->objc_rootAlloc->callAlloc->_objc_rootAllocWithZone->_class_createInstanceFromZone