对象alloc流程

247 阅读5分钟

在iOS开发中最常用的方法就是alloc方法了,今天我们就来探究下alloc底层究竟做了些什么事情 首先我们老样子,需要搞清楚alloc方法究竟是在哪个库中,我们下个alloc的符号断点,运行代码~

alloc断点

从断点中可以看到alloc方法来自libobjc,我们去下载objc库的源码继续探究。

alloc底层代码探究

  • 通过搜索alloc {符号,我们找到了alloc的底层源码实现,在方法中就调用了一个函数_objc_rootAlloc
+ (id)alloc {
    return _objc_rootAlloc(self);
}
  • 进入_objc_rootAlloc的实现后发现内部也是调用了另一个函数callAlloc
id _objc_rootAlloc(Class cls)
{
    return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}
  • callAlloc中分为两种情况,如果是OBJC2并且没有自定义alloc/allocWithZone:方法,则调用_objc_rootAllocWithZone函数,我们走的也是这个函数
static ALWAYS_INLINE id callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
#if __OBJC2__
    if (slowpath(checkNil && !cls)) return nil;
    // 👇 看自定义的类是否有自定义的alloc/allocWithZone方法,如果没有则走默认的alloc实现,即_objc_rootAllocWithZone
    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));
}
  • _objc_rootAllocWithZone函数内部依然调用了另一个函数_class_createInstanceFromZone_class_createInstanceFromZone函数也是系统最终进行对象创建的函数
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内部实现主要分为三个步骤

    • size = cls->instanceSize(extraBytes)获取实例对象需要分配的内存大小
    • obj = (id)calloc(1, size)调用calloc去堆区开辟内存空间
    • obj->initInstanceIsa(cls, hasCxxDtor)将新开辟的内存空间和cls进行关联
    • 具体源码实现如下所示,关键部分均已添加注释
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
    // 👇 是否有c++构造函数,自定义纯OC类返回false
    bool hasCxxCtor = cxxConstruct && cls->hasCxxCtor();
    // 👇 是否有c++析构函数,自定义纯OC类返回false
    bool hasCxxDtor = cls->hasCxxDtor();
    // 👇 在OBJC2中返回的基本是true
    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
        obj = (id)calloc(1, size);
    }
    if (slowpath(!obj)) {
        if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {
            return _objc_callBadAllocHandler(cls);
        }
        return nil;
    }

    // 👇 基本上走第一个,和else的区别就在于调用initIsa时中的nonpointer传true还是false
    if (!zone && fast) {
        // 👇 在这里将cls和obj关联起来,即将cls赋值到isa里面
        
        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);
}

创建实例对象的主要流程就是上述代码了,但其中还有三个关键地方的代码还没有搞清楚

获取创建实例对象所需的内存大小
  • instanceSize是用来获取创建实例对象所需内存大小的函数,内部在iOS中主要走的是fastInstanceSize函数
// 👇 获取创建实例对象所需的内存大小
inline size_t instanceSize(size_t extraBytes) const {
    // 👇 自定义的类走fastpath
    if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
        // 👇 由以下函数计算得出所需的内存空间大小
        return cache.fastInstanceSize(extraBytes);
    }

    size_t size = alignedInstanceSize() + extraBytes;
    // CF requires all objects be at least 16 bytes.
    if (size < 16) size = 16;
    return size;
}
  • fastInstanceSize的实现如下,在内部可以明显看出进行了16字节的内存对齐
size_t fastInstanceSize(size_t extra) const
{
    ASSERT(hasFastInstanceSize(extra));

    if (__builtin_constant_p(extra) && extra == 0) {
        return _flags & FAST_CACHE_ALLOC_MASK16;
    } else {
        // 👇 iOS中走else流程,size是根据结构体计算出的需要的内存空间
        size_t size = _flags & FAST_CACHE_ALLOC_MASK;
        // remove the FAST_CACHE_ALLOC_DELTA16 that was added
        // by setFastInstanceSize
        // 👇 还需要在align16函数中进行16字节内存对齐
        return align16(size + extra - FAST_CACHE_ALLOC_DELTA16);
    }
}
调用calloc去堆区开辟内存空间
  • 由于我们按住command后点击calloc无法跳转到函数实现,所以我们又在工程中添加一个calloc的符号断点,由断点可以看到calloc的实现在libsystem_malloc

注意这里不是libsystem,是malloc库,所以我们去下载malloc的源码

calloc

  • malloc源码中我们搜索calloc(size_t来到calloc的源码实现,calloc的实现比较简单,推断可知malloc_zone_calloc函数时calloc的主要实现
// 👇 calloc的源码实现
void * calloc(size_t num_items, size_t size)
{
	void *retval;
	// 👇 根据return值推断出本行代码是整个函数的重点
	retval = malloc_zone_calloc(default_zone, num_items, size);
	if (retval == NULL) {
		errno = ENOMEM;
	}
	return retval;
}
  • malloc_zone_calloc函数内部我们也可以得到一句关键代码ptr = zone->calloc(zone, num_items, size),不过此处如果点击calloc继续往下走的话会发现无法继续跟源码。如果工程可以编译的话可以在这里打个断点,lldb输入p zone->calloc会得到底层调用了default_zone_calloc函数
void * malloc_zone_calloc(malloc_zone_t *zone, size_t num_items, size_t size)
{
	MALLOC_TRACE(TRACE_calloc | DBG_FUNC_START, (uintptr_t)zone, num_items, size, 0);

	void *ptr;
	if (malloc_check_start && (malloc_check_counter++ >= malloc_check_start)) {
		internal_check();
	}

	// 👇 这里我们也根据反推的原则得到此行代码为关键代码,
	// 👉 此处调用的calloc最终会调用default_zone_calloc函数
	ptr = zone->calloc(zone, num_items, size);
	
	if (malloc_logger) {
		malloc_logger(MALLOC_LOG_TYPE_ALLOCATE | MALLOC_LOG_TYPE_HAS_ZONE | MALLOC_LOG_TYPE_CLEARED, (uintptr_t)zone,
				(uintptr_t)(num_items * size), 0, (uintptr_t)ptr, 0);
	}

	MALLOC_TRACE(TRACE_calloc | DBG_FUNC_END, (uintptr_t)zone, num_items, size, (uintptr_t)ptr);
	return ptr;
}
调用initInstanceIsa关联class
  • initInstanceIsa内部调用了initIsa函数进行初始化isa
inline void  objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
    ASSERT(!cls->instancesRequireRawIsa());
    ASSERT(hasCxxDtor == cls->hasCxxDtor());

    initIsa(cls, true, hasCxxDtor);
}
  • initIsa代码的实现我们看到了创建新的isa_t newisa(0),对newisa进行赋值,最关键的是newisa.setClass(cls, this)
inline void 
objc_object::initIsa(Class cls, bool nonpointer, UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT bool hasCxxDtor)
{ 
    ASSERT(!isTaggedPointer()); 
    
    isa_t newisa(0);

    // 👇 nonpointer为0代表isa为纯isa指针,isa中只存储类的信息
    if (!nonpointer) {
        newisa.setClass(cls, this);
    // 👇 nonpointer为1代表isa为优化后isa指针,类信息存储在isa的shift class中
    } else {
        ASSERT(!DisableNonpointerIsa);
        ASSERT(!cls->instancesRequireRawIsa());

// 👇 iOS平台SUPPORT_INDEXED_ISA未false,所以走else流程
#if SUPPORT_INDEXED_ISA
        ASSERT(cls->classArrayIndex() > 0);
        newisa.bits = ISA_INDEX_MAGIC_VALUE;
        // isa.magic is part of ISA_MAGIC_VALUE
        // isa.nonpointer is part of ISA_MAGIC_VALUE
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
        newisa.bits = ISA_MAGIC_VALUE;
        // isa.magic is part of ISA_MAGIC_VALUE
        // isa.nonpointer is part of ISA_MAGIC_VALUE
#   if ISA_HAS_CXX_DTOR_BIT
        newisa.has_cxx_dtor = hasCxxDtor;
#   endif
        // 👇 在isa中设置class信息,即将isa和cls关联起来
        newisa.setClass(cls, this);
#endif
        newisa.extra_rc = 1;
    }

    // This write must be performed in a single store in some cases
    // (for example when realizing a class because other threads
    // may simultaneously try to use the class).
    // fixme use atomics here to guarantee single-store and to
    // guarantee memory order w.r.t. the class index table
    // ...but not too atomic because we don't want to hurt instantiation
    isa = newisa;
}
  • setClass函数中我们可以清晰的看到对shiftcls进行了赋值,在赋值的时候将cls右移的三位是因为isa的低三位分别为nonpointerhas_assochas_cxxdtor
inline void
isa_t::setClass(Class newCls, UNUSED_WITHOUT_PTRAUTH objc_object *obj)
{
    // Match the conditional in isa.h.
#if __has_feature(ptrauth_calls) || TARGET_OS_SIMULATOR
#   if ISA_SIGNING_SIGN_MODE == ISA_SIGNING_SIGN_NONE
    // No signing, just use the raw pointer.
    uintptr_t signedCls = (uintptr_t)newCls;

#   elif ISA_SIGNING_SIGN_MODE == ISA_SIGNING_SIGN_ONLY_SWIFT
    // We're only signing Swift classes. Non-Swift classes just use
    // the raw pointer
    uintptr_t signedCls = (uintptr_t)newCls;
    if (newCls->isSwiftStable())
        signedCls = (uintptr_t)ptrauth_sign_unauthenticated((void *)newCls, ISA_SIGNING_KEY, ptrauth_blend_discriminator(obj, ISA_SIGNING_DISCRIMINATOR));

#   elif ISA_SIGNING_SIGN_MODE == ISA_SIGNING_SIGN_ALL
    // We're signing everything
    uintptr_t signedCls = (uintptr_t)ptrauth_sign_unauthenticated((void *)newCls, ISA_SIGNING_KEY, ptrauth_blend_discriminator(obj, ISA_SIGNING_DISCRIMINATOR));

#   else
#       error Unknown isa signing mode.
#   endif

    shiftcls_and_sig = signedCls >> 3;

#elif SUPPORT_INDEXED_ISA
    // Indexed isa only uses this method to set a raw pointer class.
    // Setting an indexed class is handled separately.
    cls = newCls;
// 👇 由于我们自定义的类基本上都是nonpointer isa,所以直接将newCls右移三位赋值给shiftcls变量
// 👇 之所以右移三位是因为前三位分别为nonpointer,has_assoc,has_cxx_dtor标志
#else // Nonpointer isa, no ptrauth
    shiftcls = (uintptr_t)newCls >> 3;
#endif
}