alloc 流程

alloc 创建对象

    HFPerson *p = [HFPerson alloc];
    HFPerson *p1 = [p init];
    HFPerson *p2 = [p init];
    
    NSLog(@"%@--%p--%p", p, p, &p);
    NSLog(@"%@--%p--%p", p1, p1, &p1);
    NSLog(@"%@--%p--%p", p2, p2, &p2);

输出结果

2021-06-15 16:59:29.125398+0800 StoreKitDemo[797:108607] --0x283b2c040--0x16b1d1028
2021-06-15 16:59:29.125476+0800 StoreKitDemo[797:108607] --0x283b2c040--0x16b1d1020
2021-06-15 16:59:29.125507+0800 StoreKitDemo[797:108607] --0x283b2c040--0x16b1d1018

从结果中可以看出p 和 p1 p2 都指向同一内存地址,可以得出一个结论就是alloc开辟了内存空间,init并没有。
注意 这边的&p,&p1,&p2 地址并不一样, 因为p 和 p1 p2是临时变量,所属空间是栈上,p 和 p1 p2 都指向的空间是alloc开辟出来的属于堆上的空间。

alloc 分析

alloc是ios/mac 系统代码,并没有开放给我们,所以我们只能通过以下几种方式来追踪
当前采用的是真机调试

1:通过符号断点加step in调试

HFPerson *p = [HFPerson alloc]; 这边下断点,control + step in 跟踪进去
StoreKitDemo`objc_alloc:   // 根据这边的objc_alloc 下符号断点
->  0x1047d28ac <+0>: nop    
    0x1047d28b0 <+4>: ldr    x16, #0x1790              ; (void *)0x00000001047d2948
    0x1047d28b4 <+8>: br     x16

0x1047d2948: ldr    w16, 0x1047d2950
    0x1047d294c: b      0x1047d2918
    0x1047d2950: udf    #0x57
    0x1047d2954: ldr    w16, 0x1047d295c
    0x1047d2958: b      0x1047d2918
    0x1047d295c: udf    #0x69
    0x1047d2960: ldr    w16, 0x1047d2968
    0x1047d2964: b      0x1047d2918

libobjc.A.dylib`objc_alloc:  
->  0x1ae8bb3e8 <+0>:  cbz    x0, 0x1ae8bb420           ; <+56>
    0x1ae8bb3ec <+4>:  ldr    x8, [x0]
    0x1ae8bb3f0 <+8>:  and    x8, x8, #0xffffffff8
    0x1ae8bb3f4 <+12>: ldrb   w8, [x8, #0x1d]
    0x1ae8bb3f8 <+16>: tbz    w8, #0x6, 0x1ae8bb400     ; <+24>
    0x1ae8bb3fc <+20>: b      0x1ae8b1afc               ; _objc_rootAllocWithZone
    0x1ae8bb400 <+24>: pacibsp 
    0x1ae8bb404 <+28>: stp    x29, x30, [sp, #-0x10]!
    0x1ae8bb408 <+32>: mov    x29, sp
    0x1ae8bb40c <+36>: adrp   x8, 228387
    0x1ae8bb410 <+40>: add    x1, x8, #0x94a            ; =0x94a 
    0x1ae8bb414 <+44>: bl     0x1ae89c040               ; objc_msgSend
    0x1ae8bb418 <+48>: ldp    x29, x30, [sp], #0x10
    0x1ae8bb41c <+52>: autibsp 
    0x1ae8bb420 <+56>: ret 

注意:真机和模拟器看到的汇编会有不同

模拟器

StoreKitDemo`objc_alloc:
->  0x10ba807bc <+0>: jmpq   *0x189e(%rip)             ; (void *)0x000000010ba80804

0x10ba80804: pushq  $0x57
    0x10ba80809: jmp    0x10ba807e0
    0x10ba8080e: pushq  $0x69
    0x10ba80813: jmp    0x10ba807e0
    0x10ba80818: pushq  $0x88
    0x10ba8081d: jmp    0x10ba807e0

2 汇编代码

image.png

通过汇编代码可以看出,[HFPerson alloc]实际调用到的是objc_alloc函数,这样我们就可以
下符号断点alloc_objc,进入到alloc_objc后再下断点_objc_rootAllocWithZone进去,一层一层跟进去,但是因为相对比较麻烦和复杂,后面我们直接通过源码分析

alloc 源码分析

1流程图

屏幕快照 2021-06-15 下午5.42.45.png
HFPerson *p = [HFPerson alloc] ;
HFPerson *p2 = [HFPerson alloc];
NSLog(@"%@",p);

2 首先符号断点到objc_alloc

id objc_alloc(Class cls) {
    return callAlloc(cls, true/*checkNil*/, false/*allocWithZone*/);
}

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

3 通过调试我们发现callAlloc会调用两次,第一次进来因为判断hasCustomAWZ为True,所以调用alloc

((id(*)(id, SEL))objc_msgSend)(cls, @selector(alloc)); 
这边的alloc调用的是
+ (id)alloc {
    return _objc_rootAlloc(self);
}
id
_objc_rootAlloc(Class cls)
{
    return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}

4 最终又回到了callAlloc此时的hasCustomAWZ为False 来到了

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

5 重点分析 _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;

    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); // 根据size 开辟空间
    }
    if (slowpath(!obj)) {
        if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {
            return _objc_callBadAllocHandler(cls);
        }
        return nil;
    }

    if (!zone && fast) {
        obj->initInstanceIsa(cls, hasCxxDtor); // 将obj 和 cls 关联(isa指针指向)
    } 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;    // 返回cls对象
    }

    construct_flags |= OBJECT_CONSTRUCT_FREE_ONFAILURE;
    return object_cxxConstructFromClass(obj, cls, construct_flags);
}

_class_createInstanceFromZone 三个重要步骤, 计算对象大小,给对象开辟空间,设置isa指针
5 如何开辟空间

inline size_t instanceSize(size_t extraBytes) const {
        // 如果已经有缓存直接返回缓存,并且16字节对齐
        if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
            return cache.fastInstanceSize(extraBytes);
        }

      // alignedInstanceSize() 计算开辟空间大小,extraBytes 前面参数传递为 0
        size_t size = alignedInstanceSize() + extraBytes;   
        // CF requires all objects be at least 16 bytes.  
        if (size < 16) size = 16;    oc对象是以16字节对齐,所以不足16字节,返回16
        return size;
    }

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

static inline size_t align16(size_t x) {
    return (x + size_t(15)) & ~size_t(15);
}

uint32_t alignedInstanceSize() const {
        return word_align(unalignedInstanceSize()); // word_align() 字节对齐,对象内部采用8字节对齐
    }

// May be unaligned depending on class's ivars. // 大小由对象的成员变量来决定
    uint32_t unalignedInstanceSize() const {
        ASSERT(isRealized());
        return data()->ro()->instanceSize; // 这边返回的是对象所有成员变量的大小
    }

#ifdef __LP64__
#   define WORD_SHIFT 3UL
#   define WORD_MASK 7UL
#   define WORD_BITS 64
#else
#   define WORD_SHIFT 2UL
#   define WORD_MASK 3UL
#   define WORD_BITS 32
#endif
static inline uint32_t word_align(uint32_t x) {
    return (x + WORD_MASK) & ~WORD_MASK;
}

@interface LGPerson : NSObject
- (void)saySomething;
@end
  1. 通过源码instanceSize分析, 一开始会判断是否有缓存cache,如果有直接通过fastInstanceSize返回对象的大小。紧接着通过代码调试,我们看到fastInstanceSize在size_t size = _flags & FAST_CACHE_ALLOC_MASK;得出的结果是16,在经过align16(size + extra - FAST_CACHE_ALLOC_DELTA16)进行16字节对齐。
  2. 返回对象大小,而这个大小由所有成员变量来决定,返回的Size通过 按照八字结对齐方式返回结果。
    总结:对象内部成员采用8字节对齐,对象与对象采用16字节对齐

6 对齐算法
(x + WORD_MASK) & ~WORD_MASK (WORD_MASK 上面宏定义为7)
如果x=5
(5 + 7)& ~7
12 & ~7
12 -> 1100
~7 -> ~0111-> 1000
1100 & 1000 -> 1000 -> 8

你可能感兴趣的:(alloc 流程)