美文网首页
iOS 底层原理 -- alloc

iOS 底层原理 -- alloc

作者: Nulll | 来源:发表于2021-06-06 09:38 被阅读0次

    前言

    作为一门面向对象的语言,用一句万物皆对象来形容OC是非常贴切的。而NSObject 几乎是有所OC对象的老祖(为什么说几乎呢而不是全部呢?因为哈呦一个叫做NSProxy 的抽象类);但凡是使用到对象你需要先对对象进行初始化吧,那今天就来讨论一下alloc到底做了什么。


    alloc 原理流程

    1、先来看一个例子:

        CDSubObj *obj1 = [CDSubObj alloc];
        CDSubObj *obj2 = [obj1 init];
        CDSubObj *obj3 = [obj1 init]; 
        CDSubObj *obj4 = [CDSubObj alloc];
        
        NSLog(@"%@->%p", obj1, obj1);
        NSLog(@"%@->%p", obj2, obj2);
        NSLog(@"%@->%p", obj3, obj3);
        NSLog(@"%@->%p", obj4, obj4);
        
    //结果如下:
    <CDSubObj: 0x6000036e42d0>->0x6000036e42d0
    <CDSubObj: 0x6000036e42d0>->0x6000036e42d0
    <CDSubObj: 0x6000036e42d0>->0x6000036e42d0
    <CDSubObj: 0x6000036f0090>->0x6000036f0090
    

    结论:可以明显看到obj1、2、3 的内存地址的一样的。而obj4 是一块单独的内存地址。

    2、我们再来看看每个的指针地址又是如何的。

        NSLog(@"%@->%p ->%p", obj1, obj1, &obj1);
        NSLog(@"%@->%p ->%p", obj2, obj2, &obj2);
        NSLog(@"%@->%p ->%p", obj3, obj3, &obj3);
        NSLog(@"%@->%p ->%p", obj4, obj4, &obj4);
    
    //结果:
    <CDSubObj: 0x600002bf4110>->0x600002bf4110 ->0x16faa54f8
    <CDSubObj: 0x600002bf4110>->0x600002bf4110 ->0x16faa54f0
    <CDSubObj: 0x600002bf4110>->0x600002bf4110 ->0x16faa54e8
    <CDSubObj: 0x600002be0130>->0x600002be0130 ->0x16faa54e0
    

    结论:明显看到每个的指针地址也是不一样的。而且依次相差8字节,且是高地址(栈空间)

    alloc 源码分析流程

    上面看到了,alloc 确实是开辟了内存空间。而init 貌似什么都没做。
    接下来从源码的角度来分析:这里需要准备一份编译好objc 源码:objc818

    1、打开项目,初始化一个对象并且下一个断点:

    下断点

    2、点击alloc 方法,会跳转到NSObject.mm 里面的 +alloc 方法,里面调用了一个 _objc_rootAlloc()的方法

    NSObject.mm的alloc方法

    3、继续进入:_objc_rootAlloc 方法里面调用了 callAlloc(),接下来看看callAlloc 到底干了什么事。

    /// callAlloc 方法的源码:
    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));
    }
    
    //(!cls->ISA()->hasCustomAWZ()) 这个第一次的时候 为false ,直接走了消息发送的流程。然后在回来执行 _objc_rootAllocWithZone()
    
    

    4、_objc_rootAllocWithZone()

    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);
    }
    // _objc_rootAllocWithZone里面只调用了 _class_createInstanceFromZone 这个方法。
    

    _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;
    
      //1、获取当前对象需要的内存大小
        size = cls->instanceSize(extraBytes);
        if (outAllocatedSize) *outAllocatedSize = size;
    
    //2、调用calloc开辟内存
        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;
        }
    
    //3、绑定类名信息
        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);
        }
    
    //4、返回对象
        if (fastpath(!hasCxxCtor)) {
            return obj;
        }
    
        construct_flags |= OBJECT_CONSTRUCT_FREE_ONFAILURE;
        return object_cxxConstructFromClass(obj, cls, construct_flags);
    }
    
    

    4.1: size = cls->instanceSize(extraBytes); 这个计算对象到底需要分配多少字节的内存。

    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;
        }
    
    

    4.2 从缓存当中获取内存大小。这里有一个对其方式 为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 {
                size_t size = _flags & FAST_CACHE_ALLOC_MASK;
                // remove the FAST_CACHE_ALLOC_DELTA16 that was added
                // by setFastInstanceSize
                return align16(size + extra - FAST_CACHE_ALLOC_DELTA16);
            }
        }
    
    // (x + 15) &  ~15
    // 例如:x = 8; x + 15 = 23;
    //23     :           0001 0111
    //15     :              0000 1111
    //~15  :             1111 0000
    //23 & ~15 =    0001 0000 == 16
    //这里就是16字节对齐原理
    static inline size_t align16(size_t x) {
        return (x + size_t(15)) & ~size_t(15);
    }
    

    4.3: 处理字节对齐的情况
    由于WORD_MASK 是7.
    (x + WORD_MASK) & ~WORD_MASK; 这个就相当是8字节对齐;
    这里的7字节对齐和15字节对齐原理是一样的。

    uint32_t unalignedInstanceSize() const {
            ASSERT(isRealized());
            return data()->ro()->instanceSize;
        } 
        uint32_t alignedInstanceSize() const {
            return word_align(unalignedInstanceSize());
        }
    
    define WORD_MASK 7UL
    static inline uint32_t word_align(uint32_t x) {
        return (x + WORD_MASK) & ~WORD_MASK;
    }
    
    

    1)当我整个对象为空的时候:

    @interface CDSubObj : NSObject
    
    @end
    //输出结果
    (lldb) p cls
    (Class) $0 = CDSubObj
    (lldb) p size
    (size_t) $1 = 16
    
    1. 当这个对象增加一些属性后
    @interface CDSubObj : NSObject
    @property (nonatomic, copy) NSString *name;
    @property (nonatomic, assign) int age;  
    
    @end
    //输出结果
    (lldb) p cls
    (Class) $0 = CDSubObj
    (lldb) p size
    (size_t) $1 = 32
    

    结论:可以看出内存的大小是按照16字节对齐的。

    5、获取到需要开辟的内存后,调用 calloc 开辟内存

    id obj;
        if (zone) {
            obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
        } else {
            obj = (id)calloc(1, size);
        }
    

    6、调用 initInstanceIsa 绑定isa。然后返回对象

    inline void 
    objc_object::initIsa(Class cls, bool nonpointer, UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT bool hasCxxDtor)
    { 
        ASSERT(!isTaggedPointer()); 
        
        isa_t newisa(0);
    
        if (!nonpointer) {
            newisa.setClass(cls, this);
        } else {
            ASSERT(!DisableNonpointerIsa);
            ASSERT(!cls->instancesRequireRawIsa());
    
    
    #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
            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;
    }
    

    结论:

    整个alloc的流程大致就是NSObject +alloc->_objc_rootAlloc->callAlloc->hasCustomAWZ == false->objc_msgSend ;或者hasCustomAWZ == true->_objc_rootAllocWithZone->_class_createInstanceFromZone->instanceSize->calloc->initInstanceIsa

    相关文章

      网友评论

          本文标题:iOS 底层原理 -- alloc

          本文链接:https://www.haomeiwen.com/subject/jezhjltx.html