在iOS开发中最常用的方法就是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的源码
- 在
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的低三位分别为nonpointer、has_assoc和has_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
}