OC底层原理-01-alloc底层探究

1,244

这篇博客主要讲OC对象alloc的流程, 主要分两步: 第1:怎样探寻alloc方法的执行流程 第2:分析alloc函数执行流程

下面是OC对象alloc的流程图

1.png

一:怎样探寻alloc方法的执行流程

如上图所示,是一个OC对象alloc流程中所调用的一些方法。

  1. 我们可以直接通过xcode符号断点的方式,设置符号断点,结合xcode(按住command,点击step into 进入)

2.png

image.png

image.png

我们在想要查看的某个对象实例化的那行代码打上断点(由于程序运行起来会有大量的对象被创建,去除干扰,我们可以先将符号断点disable,当运行到断点时打开) 此时我们可以看见alloc内部调用了objc_alloc,接着我们急可以直接设置符号断点。拦截objc_alloc函数,查看内部调用的什么方法,以此类推

2.通过反汇编调试,查看调用链

image.png

选择 Always Show Disassembly,快速定位到objc_alloc结合符号断点,按住command,点击step into 进入,单步执行.

image.png

image.png

3.我们直接通过源码分析,我们通过上面的符号断点和反汇编调试,知道了objc_alloclibobjc.A.dylib中的函数, 我们去apple open source下载源码编译,单步调试。

二:分析alloc函数执行流程

image.png

当实例化一个NSObject类的实例,反汇编+源码调试,会发现并没有执行NSObject的类方法alloc,而是直接执行的objc_alloc

1. alloc

+ (id)alloc {
    return _objc_rootAlloc(self);
}

2. objc_alloc

id objc_alloc(Class cls)
{
    return callAlloc(cls, true/*checkNil*/, false/*allocWithZone*/);
}

callAlloc

static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
#if __OBJC2__
    if (slowpath(checkNil && !cls)) return nil;
    if (fastpath(!cls->ISA()->hasCustomAWZ())) {
        return _objc_rootAllocWithZone(cls, nil);
    }
#endif

    // No shortcuts available.
    if (allocWithZone) {
        return ((id(*)(id, SEL, struct _NSZone *))objc_msgSend)(cls, @selector(allocWithZone:), nil);
    }
    return ((id(*)(id, SEL))objc_msgSend)(cls, @selector(alloc));
}

callAlloc方法中,会发现会进入if (fastpath(!cls->ISA()->hasCustomAWZ()))

inline Class
objc_object::ISA(bool authenticated)
{
    ASSERT(!isTaggedPointer());
    return isa.getDecodedClass(authenticated);
}

// class or superclass has default alloc/allocWithZone: implementation
// Note this is is stored in the metaclass.
#define FAST_CACHE_HAS_DEFAULT_AWZ    (1<<14)
bool hasCustomAWZ() const {
    return !cache.getBit(FAST_CACHE_HAS_DEFAULT_AWZ);
}

由此可以看出,当调用callAlloc,先通过当前cls的ISA 返回一个Class对象,然后判断当前cache中是否有缓存,如果有,执行_objc_rootAllocWithZone,如果没有,执行objc_msgSend, 继续运行程序, 会发现NSObject在cache中已经有缓存,直接执行了_objc_rootAllocWithZone

_objc_rootAllocWithZone

id
_objc_rootAllocWithZone(Class cls, malloc_zone_t *zone __unused)
{
    // allocWithZone under __OBJC2__ ignores the zone parameter
    return _class_createInstanceFromZone(cls, 0, nil,
                                         OBJECT_CONSTRUCT_CALL_BADALLOC);
}

_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;

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

_objc_rootAllocWithZone -> _class_createInstanceFromZone 创建一个类的实例,分三步:

1.cls->instanceSize 计算对象所需要的的内存大小

2.cls->calloc 申请开辟内存

3.cls->initInstanceIsa 通过isa,将对象于类关联

然后直接返回obj

添加一下自定义类型调用alloc

image.png

image.png 会发现p1实例在alloc执行流程中,cache中没有缓存,使用runtime objc_msgSend函数调用了alloc方法,然后再调用 _objc_rootAlloc -> callAlloc -> _objc_rootAllocWithZone -> _class_createInstanceFromZone -> instanceSize -> calloc -> initInstanceIsa 然后跳过p1, 会发现p2的alloc执行流程与上面的NSObject一样,就不会通过runtime的 objc_msgSend调用alloc方法,因为此时缓存中已经有LGPerson的缓存了,就直接 _objc_rootAllocWithZone执行后面的流程

总结

一个自定义类alloc的过程: 通过调用objc_alloc->callAlloc,在callAlloc内部分有缓存无缓存两种情况,当有缓存时通过 _objc_rootAllocWithZone -> _class_createInstanceFromZone -> instanceSize -> calloc -> initInstanceIsa 无缓存是,callAlloc内部通过objc_msgSend 调用alloc方法, 在alloc方法内部执行 _objc_rootAlloc -> callAlloc,后面步骤与上相同。