iOS对象的底层探索(上)

iOS对象的底层探索(上),第1张

在探索OC对象底层本质之前,先了解一下clang

Clang

clang是⼀个由Apple主导编写,基于LLVM的C/C++/Objective-C编译器主要用于底层编译,将oc文件转换成c++文件,方便理解底层原理


OC是面向对象的语言,开发中一切的基础,首先需要一个对象,如果没有的话,可以alloc一个。本篇文章主要探索alloc的内部执行流程。

首先自定义Person继承与NSObject

//创建对象
 Person *person = [[Person alloc] init];

断点在该行,control + step into进入断点即可看到调用objc_alloc函数,再源码如下

alloc调用一次向下执行

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

// Base class implementation of +alloc. cls is not nil.
// Calls [cls allocWithZone:nil].
id
_objc_rootAlloc(Class cls)
{
    return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}

//alloc源码
// Call [cls alloc] or [cls allocWithZone:nil], with appropriate
// shortcutting optimizations.
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));
}
NEVER_INLINE
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);
}

//oc源码
/***********************************************************************
* class_createInstance
* fixme
* Locking: none
*
* Note: this function has been carefully written so that the fastpath
* takes no branch.
**********************************************************************/
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);
}

理论alloc执行流程:

alloc->_objc_rootAlloc->callAlloc->_objc_rootAllocWithZone->_class_createInstanceFromZone

 但其实并非如此,貌似被欺骗了,接下来按debug一步一步跟踪调试进行探究。

实际debug后alloc的执行流程: 1、代码首先走到对象alloc的地方,然后起开所有断点进行一步一步调试

 2.代码首先进入callAlloc并直接最后一步return消息发送((id(*)(id, SEL))objc_msgSend)(cls, @selector(alloc))

 3、执行alloc *** 作,并调用_objc_rootAlloc  4、调用_objc_rootAlloc函数,_objc_rootAlloc内部再次调用callAlloc

 5、callAlloc执行流程

6、调用_objc_rootAllocWithZone

 7、调用_class_createInstanceFromZone,计算内存大小,calloc开辟内存空间,关联类和内存

callAlloc->objc_msgSend->alloc->_objc_rootAlloc->callAlloc->objc_rootAllocWithZone->_class_createInstanceFromZone->calloc

_class_createInstanceFromZone 中计算内存的方法 size= cls->instanceSize(extraBytes);具体是根据CPU64位还是32位进行字节对齐。

字节对照表

 

 结构体内存对齐的规则

1:数据成员对齐规则:结构(struct)的第一个数据成员放在offset为0的地方,以后每个数据成员存储的起始位置要从该成员大小或者成员的子成员大小的整数倍开始(比如int为4字节,则要从4的整数倍地址开始存储)。

 2:结构体作为成员:如果一个结构里有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储.(struct a里存有struct b,b里有char,int ,double等元素,那b应该从8的整数倍开始存储)。

 3:收尾工作:结构体的总大小,也就是sizeof的结果必须是其内部最大成员的整数倍,不足的要补齐。

以下验证字节对齐规则:

 通过字节对齐算法,我们计算得出 struct1=24,struct2=16,struct3=48

PS调试过程中出现断点跳过不执行并且变虚线的情况

解决办法target的build phases里面的main顺序设置到第一个,具体原因参考:Xcode 13: Active breakpoint turns … | Apple Developer Forums


 

总结

综上的现象,我们可知alloc()方法实现了对象的内存分配,内存对齐,将对象和类型绑定三个功能。 

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/994647.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-21
下一篇 2022-05-21

发表评论

登录后才能评论

评论列表(0条)

保存