美文网首页
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