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 汇编代码
通过汇编代码可以看出,[HFPerson alloc]实际调用到的是objc_alloc函数,这样我们就可以
下符号断点alloc_objc,进入到alloc_objc后再下断点_objc_rootAllocWithZone进去,一层一层跟进去,但是因为相对比较麻烦和复杂,后面我们直接通过源码分析
alloc 源码分析
1流程图
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
- 通过源码instanceSize分析, 一开始会判断是否有缓存cache,如果有直接通过fastInstanceSize返回对象的大小。紧接着通过代码调试,我们看到fastInstanceSize在size_t size = _flags & FAST_CACHE_ALLOC_MASK;得出的结果是16,在经过align16(size + extra - FAST_CACHE_ALLOC_DELTA16)进行16字节对齐。
- 返回对象大小,而这个大小由所有成员变量来决定,返回的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