iOS-底层原理:alloc & init & new 源码分析

2021-01-03  本文已影响0人  云霄_云霄

写在前面:本文并非原创,再此使用也仅为学习记录,以便后期复习,原文作者:Style_月月,地址:https://www.jianshu.com/p/b72018e88a97

一、学习底层原理,我们首先要找到Apple开源的代码库地址:

1、Apple 所有开源源码汇总地址,根据相应的版本查找对应的源码,以mac 10.15为例: macOS --> 10.15 --> 选择10.15 --> 搜索 objc
2、Apple 比较直接的源码下载地址,直接搜索想要下载的源码名称即可,例如objc直接搜索 objc --> objc4/ --> 选择相应的objc的版本

二、源码分析

在分析alloc源码之前,先来看看一下3个变量 内存地址 和 指针地址区别: 内存地址与指针地址.png

分别输出3个对象的内容、内存地址、指针地址,下图是打印结果

image.png
结论:通过上图可以看出,3个对象指向的是同一个内存空间,所以其内容内存地址是相同的,但是对象的指针地址是不同的
补充
%p -> &p1:是对象的指针地址
%p -> p1: 是对象指针指向的的内存地址

这就是本文需要探索的内容,alloc做了什么?init做了什么?

三、alloc 源码探索

alloc + init 整体源码的探索流程如下


alloc + init探索流程.png
//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*/);
}
static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)// alloc 源码 第三步
{
#if __OBJC2__ //有可用的编译器优化
    /*
     参考链接:https://www.jianshu.com/p/536824702ab6
     */
    
    // checkNil 为false,!cls 也为false ,所以slowpath 为 false,假值判断不会走到if里面,即不会返回nil
    if (slowpath(checkNil && !cls)) return nil;
    
    //判断一个类是否有自定义的 +allocWithZone 实现,没有则走到if里面的实现
    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));
}

如上所示,在calloc方法中,当我们无法确定实现走到哪步时,可以通过断点调试,判断执行走哪部分逻辑。这里是执行到_objc_rootAllocWithZone

id
_objc_rootAllocWithZone(Class cls, malloc_zone_t *zone __unused)// alloc 源码 第四步
{
    // allocWithZone under __OBJC2__ ignores the zone parameter
    //zone 参数不再使用 类创建实例内存空间
    return _class_createInstanceFromZone(cls, 0, nil,
                                         OBJECT_CONSTRUCT_CALL_BADALLOC);
}
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)// alloc 源码 第五步
{
    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;

    //计算需要开辟的内存大小,传入的extraBytes 为 0
    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) {
        //将 cls类 与 obj指针(即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);
}
_class_createInstanceFromZone流程.png

四、内存字节对齐原则

Apple的分配内存是16字节对齐,在解释为什么需要16字节对齐之前,首先需要了解内存字节对齐的原则,主要有以下三点

为什么需要16字节对齐
需要字节对齐的原因,有以下几点:

五、init 源码探索

alloc源码探索完了,接下来探索init源码,通过源码可知,inti的源码实现有以下两种
类方法 init

+ (id)init {
    return (id)self;
}

这里的init是一个构造方法 ,是通过工厂设计(工厂方法模式),主要是用于给用户提供构造方法入口。这里能使用id强转的原因,主要还是因为内存字节对齐后,可以使用类型强转为你所需的类型
实例方法 init

LGPerson *objc = [[LGPerson alloc] init];
- (id)init {
    return _objc_rootInit(self);
}
id
_objc_rootInit(id obj)
{
    // In practice, it will be hard to rely on this function.
    // Many classes do not properly chain -init calls.
    return obj;
}

有上述代码可以,返回的是传入的self本身。

六、new源码探索

一般在开发中,初始化除了init,还可以使用new,两者本质上并没有什么区别,以下是objc中new的源码实现,通过源码可以得知,new函数中直接调用了callAlloc函数(即alloc中分析的函数),且调用了init函数,所以可以得出new其实就等价于[alloc init]的结论。

+ (id)new {
    return [callAlloc(self, false/*checkNil*/) init];
}

但是一般开发中并不建议使用new,主要是因为有时会重写init方法做一些自定义的操作,例如initWithXXX,会在这个方法中调用[super init],用new初始化可能会无法走到自定义的initWithXXX部分。

总结

上一篇 下一篇

猜你喜欢

热点阅读