ios alloc-对象的创建与内存分配

1.0 oc对象的创建

先来思考几个问题:1、alloc、init、new,初始化对象是不是都会分配内存?2、alloc int new 有什么区别?内存是否一样?3、一个对象最少占用多少内存?4、对象创建的流程是怎样的?

做个demo测试一下:

    LGPerson *p1 = [LGPerson alloc];
    LGPerson *p2 = [LGPerson new];
    LGPerson *p3 = [p1 init];
    LGPerson *p4 = [p1 init];
    
    NSLog(@"%@-%p-%p-size:%ld",p1,p1,&p1,sizeof(p1));
    NSLog(@"%@-%p-%p-size:%ld",p2,p2,&p2,sizeof(p2));
    NSLog(@"%@-%p-%p-size:%ld",p3,p3,&p3,sizeof(p3));
    NSLog(@"%@-%p-%p-size:%ld",p4,p4,&p4,sizeof(p4));
复制代码

输出结果如下:

<LGPerson: 0x283a848e0>-0x283a848e0-0x16fc11af8-size:8
<LGPerson: 0x283a848c0>-0x283a848c0-0x16fc11af0-size:8
<LGPerson: 0x283a848e0>-0x283a848e0-0x16fc11ae8-size:8
<LGPerson: 0x283a848e0>-0x283a848e0-0x16fc11ae0-size:8
复制代码

上面可以看出:1、alloc和new 都会初始化对象并分配内存空间,指向alloc所创建对象的指针的首地址是0x281abc3b0,指向new所创建对象的指针的首地址是0x281abc3c0。2、alloc和init的指针指向同一块内存区域都是从0x281abc3b0开始 3、指针是存放地址的一个变量,0x16ee95af8、0x16ee95af0、0x16ee95ae8、0x16ee95ae0是4个指针变量的地址

总结:allocnew创建对象都会分配内存空间,alloc/int指向同一块内存空间,new开辟了新内存空间。

那么alloc和new到底如何创建对象并分配内存的呢?
简单粗暴一点直接上源码断点调试,源码地址:objc4,在此感谢Cooci老师提供的可调式源码~~

2.0 alloc执行过程

先从几段重要的源码开始分析

2.0.1 callAlloc

static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
#if __OBJC2__
    if (slowpath(checkNil && !cls)) return nil;
    // 如果有自定义的allocWithZone(hasCustomAWZ) 大概率走这个方法
    if (fastpath(!cls->ISA()->hasCustomAWZ())) {
        return _objc_rootAllocWithZone(cls, nil);
    }
#endif

    //allocWithZone==true
    if (allocWithZone) {
        return ((id(*)(id, SEL, struct _NSZone *))objc_msgSend)(cls, @selector(allocWithZone:), nil);
    }
    return ((id(*)(id, SEL))objc_msgSend)(cls, @selector(alloc));
}
复制代码

分析:根据callAlloc参数判断出会进入_objc_rootAllocWithZone方法

2.0.2 _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;
    //在iOS中,字节是8自己对齐,而内存是16字节对齐,所以小于16字节会补齐
    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) {
        //关联对象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);
}
复制代码

分析:先计算出对象所需内存空间,内存开辟都是16个字节进行对齐的,然后calloc开辟内存,最后关联对象isa。

2.0.3 instanceSize

  inline size_t instanceSize(size_t extraBytes) const {
        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;
    }
复制代码

分析:word_align,align16,这两个计算方式决定了ios中内存按16个字节对齐,字节按8字节对齐。也就是说创建一个对象至少需要开辟16个字节的内存空间

2.0.3 流程图分析

截屏2021-06-13 下午1.51.28.png

该图展示了alloc创建对象的执行流程。

2.0.4 calloc两次执行

在断点调试的时候,发现calloc执行了两次?通过调试与参阅资料发现系统内部会通过llvm的函数方法把alloc指向到objc_alloc,在objc_alloc中又调用了callAlloc(cls, true, false),最后objc_msgSend转发alloc,执行alloc方法。

static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
#if __OB
     //通过参数判断不执行此处代码
    if (slowpath(checkNil && !cls)) return nil;
     //通过参数判断不执行此处代码
    if (fastpath(!cls->ISA()->hasCustomAWZ())) {
        return _objc_rootAllocWithZone(cls, nil); 
    }
#endif

    // 传入的参数为false 此处不执行
    if (allocWithZone) {
        return ((id(*)(id, SEL, struct _NSZone *))objc_msgSend)(cls, @selector(allocWithZone:), nil); 
    }
    //执行objc_msgsend 转发alloc
    return ((id(*)(id, SEL))objc_msgSend)(cls, @selector(alloc)); 
}
复制代码

2.0.5 alloc详细流程补充

通过2.0.4我们知道了llvm系统内部会优先执行一次alloc方法,分析源码补充一张allo详细执行流程图。

截屏2021-06-13 下午3.11.32.png

3.0 new与init执行过程

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

我们看到new的实现就是执行了,callAlloc(),然后执行了init操作。等价于[[XX alloc] init]

4.0 总结

1、alloc、new,都会初始化对象并分配内存?
2、alloc+int等价于new
3、一个对象最少占用16个字节内存

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享