OC对象alloc和init源码探索

592 阅读3分钟

一、OC对象初始化

AppleSVP *a1 = [AppleSVP alloc];
AppleSVP *a2 = [a1 init];
AppleSVP *a3 = [a1 init];

打印指针:

打印指针
JONYLog(@"%@--%p",a1,a1);
JONYLog(@"%@--%p",a2,a2);
JONYLog(@"%@--%p",a3,a3);

结果:

Jony : alloc init 开辟内存探索
<AppleSVP: 0x600000bbc270>--0x600000bbc270
<AppleSVP: 0x600000bbc270>--0x600000bbc270
<AppleSVP: 0x600000bbc270>--0x600000bbc270

结论: alloc开辟内存空间后,init并没有开辟新的内存空间,地址一抹抹一样样。 再打印指针本身的地址:

JONYLog(@"%@--%p--%p",a1,a1,&a1);
JONYLog(@"%@--%p--%p",a2,a2,&a2);
JONYLog(@"%@--%p--%p",a3,a3,&a3);

打印结果为:

Jony : alloc init 开辟内存探索
<AppleSVP: 0x60000108c190>--0x60000108c190--0x16f283f08
<AppleSVP: 0x60000108c190>--0x60000108c190--0x16f283f00
<AppleSVP: 0x60000108c190>--0x60000108c190--0x16f283ef8

结论: 指针自身地址并不相同,并且相隔为8。

二、符号断点调试

第一种:添加符号断点objc_alloc

断点_01.png 启动重跑会来到 objc_alloc

断点_02.png 第二种:对象alloc处添加断点

断点_03.png

打开汇编调试代码 初探_04.png

第三种:通过已知符号独断添加alloc符号断点,确定未知

初探_05.png 可得:[NSObject alloc]

三、底层源码调试

官网下载objc源码:

01_1.jpg 开始源码调试,搜索alloc方法,定位到NSObject.mm混编代码: 01_2.jpg 定位代码:

+ (id)alloc {
    return _objc_rootAlloc(self);
}

点进去:

id _objc_rootAlloc(Class cls)
{
    return callAlloc(cls, false/*checkNil*/, true/*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

    // N o 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));
}

此时究竟是跑_objc_rootAllocWithZone方法还是objc_msgSend方法,现在还不得而知

IMG_2773.PNG 先给代码打上断点 01_3.jpg 再添加3个断点 01_6.jpg 点击来到汇编: 1_7.jpg 可以看到,先走了_objc_rootAllocWithZone,再走objc_msgSend 重新走一遍: 首先来到_objc_rootAlloc 01_7.jpg 再来到_objc_rootAllocWithZone 01_8.jpg 并没有经过callAlloc断点

01_9.jpg 原因: 编译器优化

打个断点,重跑一次 01_10.jpg 添加断点 01_11.jpg 点进去源码,再添加断点 01_12.jpg 跑一下,打一下当前对象地址

(lldb) p obj
(id) $1 = 0x00000001000083b0
(lldb) 

再点击往下走

01_14.jpg 此时,p当前的obj对象

(lldb) p obj
(id) $1 = 0x00000001000083b0
(lldb) p obj
(id) $2 = 0x0000000100e04080
(lldb) 

结论: 发现对象的地址变了,此时重新分配了新的内存空间,上面的内存数据是脏内存。 在isaInit前面添加断点: 01_15.jpg 往下走,断点已经走完isaInit 01_16.jpg 此时再p一下obj,发现已经绑定了类:

(lldb) p obj
(id) $1 = 0x00000001000083b0
(lldb) p obj
(id) $2 = 0x0000000100e04080
(lldb) p obj
(ApplePerson *) $3 = 0x0000000100e04080
(lldb) 

结论: 此时可以看到,p obj对象已经绑定了ApplePerson

四、lldb调试

重跑一下工程:

01_17.jpg

添加断点,并跑到断点: 01_18.jpg 此时,extraBytes是0 image.png 点进instanceSize方法,添加断点 image.png 可以看到,如果size小于16就等于16,还有字节对齐方法alignedInstanceSize(),点进去

 uint32_t alignedInstanceSize() const {
        return word_align(unalignedInstanceSize());
    }

再点进去,字节对齐算法:

static inline uint32_t word_align(uint32_t x) {
    return (x + WORD_MASK) & ~WORD_MASK;
}

8字节对齐计算

(8 + 7) & ~ 7  = 15 & ~ 7  = 8字节对齐 取8的整数
0000 1111
1111 1000 (7取反)
0000 1000 结果= 8

实际就是:右移3位,再左移3位
(8 + 7) >>3 再 <<3


0000 0111 (7)

查看内存

image.png x p一下

(lldb) x p
0x1011aa940: b1 83 00 00 a1 21 00 00 00 00 00 00 00 00 00 00  .....!..........
0x1011aa950: 2d 5b 4e 53 54 6f 6f 6c 62 61 72 43 6f 6c 6c 65  -[NSToolbarColle
(lldb) 

因为是ios是小端模式,内存从左往右读,可以通过地址查看内存情况:

image.png p一下地址

(lldb) p 0x21a1000083b1
(long) $1 = 36975373484977
(lldb) 

拼上isa_mask po一下

(lldb) x p
0x1011aa940: b1 83 00 00 a1 21 00 00 00 00 00 00 00 00 00 00  .....!..........
0x1011aa950: 2d 5b 4e 53 54 6f 6f 6c 62 61 72 43 6f 6c 6c 65  -[NSToolbarColle
(lldb) p 0x21a1000083b1
(long) $1 = 36975373484977
(lldb) po 0x21a1000083b1 & 0x0000000ffffffff8
ApplePerson

NSLog添加断点调试

ApplePerson *p = [ApplePerson alloc] ;
p.name      = @"kc";
p.nickName  = @"细细";
p.age       = 18;
p.height    = 10;
NSLog(@"%@",p);

打印内存:

(lldb) x/6gx p
0x1011c46a0: 0x000021a100008499 0x0000000a00000012
0x1011c46b0: 0x0000000100004008 0x0000000100004028
0x1011c46c0: 0x000000020cc639d8 0x0000000000000000
(lldb) po 0x0000000a
10
(lldb) po 0x00000012
18
(lldb) po 0x0000000100004008
kc
(lldb) po 0x0000000100004028
细细
(lldb) po 0x000000020cc639d8
8804252120
(lldb) po 0x0000000000000000
<nil>
(lldb) 

可以看到10和18公用一个8字节地址,这就是字节对齐 通过上面:最终我们绘制出对象的alloc和init的流程图 image.png