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] <HFPerson: 0x283b2c040>--0x283b2c040--0x16b1d1028
2021-06-15 16:59:29.125476+0800 StoreKitDemo[797:108607] <HFPerson: 0x283b2c040>--0x283b2c040--0x16b1d1020
2021-06-15 16:59:29.125507+0800 StoreKitDemo[797:108607] <HFPerson: 0x283b2c040>--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 三个重要步骤,\color{#ff0000} {instanceSize} 计算对象大小,\color{#ff0000} {calloc}给对象开辟空间,\color{#ff0000} {initInstanceIsa}设置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. \color{#ff0000}{unalignedInstanceSize} 返回对象大小,而这个大小由所有成员变量来决定,返回的Size通过\color{#f00}{word_align} 按照八字结对齐方式返回结果。
    总结:对象内部成员采用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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,496评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,407评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,632评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,180评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,198评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,165评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,052评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,910评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,324评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,542评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,711评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,424评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,017评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,668评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,823评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,722评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,611评论 2 353

推荐阅读更多精彩内容