前言
未来的你,会感谢现在努力的自己。每一份努力都是未来人生道路的财富!
指针地址和内存地址
YYObjet *p1 = [YYObjet alloc];
YYObjet *p2 = [p1 init];
YYObjet *p3 = [p1 init];
YYObjet *p4 = [YYObjet alloc];
NSLog(@"%@-%p-%p",p1,p1,&p1);
NSLog(@"%@-%p-%p",p2,p2,&p2);
NSLog(@"%@-%p-%p",p3,p3,&p3);
NSLog(@"%@-%p-%p",p4,p4,&p4);
<YYObjet: 0x6000025ec780>-0x6000025ec780-0x7ffeef1a1068
<YYObjet: 0x6000025ec780>-0x6000025ec780-0x7ffeef1a1060
<YYObjet: 0x6000025ec780>-0x6000025ec780-0x7ffeef1a1058
<YYObjet: 0x6000025ec7b0>-0x6000025ec7b0-0x7ffeef1a1050
通过对比得到:
- p1,p2,p3 对象内存地址相同但是指针地址不同
- p4 和 p1,p2,p3 对象内存地址和指针地址都是不同
alloc 开辟内存空间,init 并没有开辟内存的操作,通过init的源码可以直观的看的出来。
底层探索的三种方法
1.符号断点
操作步骤
- xcode 自带调试工具:
Ctrl+Step into - 加入符号断点
libobjc.A.dylib objc_alloc:
得到结果
2.汇编
3.符号断点 确定未知
- 简单来说:因为我们已经确定会调用
alloc,所有直接对其加符号断点
得到结果
通过以上三种调试方式,断点断在libobjc.A.dylib。可以在苹果开源网站下载对应的开源源码,对应的配置流程工程网上也有很多,可以下载调试[开车链接],更多的关注alloc的创建流程。
alloc 创建流程
通过流程跟踪,发现最终调用了 _class_createInstanceFromZone
可以用流程图更加清晰的表达
_class_createInstanceFromZone
_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); //开辟内存空间
}
if (slowpath(!obj)) {
if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {
return _objc_callBadAllocHandler(cls);
}
return nil;
}
if (!zone && fast) {
obj->initInstanceIsa(cls, hasCxxDtor);//初始化isa和cls绑定
} 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;
}
construct_flags |= OBJECT_CONSTRUCT_FREE_ONFAILURE;
return object_cxxConstructFromClass(obj, cls, construct_flags);
}
instanceSize
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;
}
fastInstanceSize
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); //字节对齐
}
}
align1616字节对齐
static inline size_t align16(size_t x) {
return (x + size_t(15)) & ~size_t(15);
}
以10为例:
align16(10){
=(10 + size_t(15)) & ~size_t(15)
=(10 + 15)& ~ 15
= 25 & ~ 15
= 11001 & ~ 01111 //转化为二进制
= 11001 & 10000
= 10000 (十进制:16)
}
但是真的是按照Jump to definition 的步骤走的么?看下边下边这个gif图就会发现,断点会先断objc_alloc,而并不是先断_objc_rootAlloc
alloc -> objc_alloc
fixupMessageRef(message_ref_t *msg)
{
msg->sel = sel_registerName((const char *)msg->sel);
if (msg->imp == &objc_msgSend_fixup) {
if (msg->sel == @selector(alloc)) {
msg->imp = (IMP)&objc_alloc; //绑定修复
} else if (msg->sel == @selector(allocWithZone:)) {
msg->imp = (IMP)&objc_allocWithZone;
} else if (msg->sel == @selector(retain)) {
msg->imp = (IMP)&objc_retain;
} else if (msg->sel == @selector(release)) {
msg->imp = (IMP)&objc_release;
} else if (msg->sel == @selector(autorelease)) {
msg->imp = (IMP)&objc_autorelease;
} else {
msg->imp = &objc_msgSend_fixedup;
}
}
else if (msg->imp == &objc_msgSendSuper2_fixup) {
msg->imp = &objc_msgSendSuper2_fixedup;
}
else if (msg->imp == &objc_msgSend_stret_fixup) {
msg->imp = &objc_msgSend_stret_fixedup;
}
else if (msg->imp == &objc_msgSendSuper2_stret_fixup) {
msg->imp = &objc_msgSendSuper2_stret_fixedup;
}
#if defined(__i386__) || defined(__x86_64__)
else if (msg->imp == &objc_msgSend_fpret_fixup) {
msg->imp = &objc_msgSend_fpret_fixedup;
}
#endif
#if defined(__x86_64__)
else if (msg->imp == &objc_msgSend_fp2ret_fixup) {
msg->imp = &objc_msgSend_fp2ret_fixedup;
}
#endif
}
从fixupMessageRef方法中可以看出,如果当前的sel == @selector(alloc),那么 imp = (IMP)&objc_alloc,在这里sel和imp重新发生绑定。编译器LLVM中会更加详细的介绍,有兴趣可以查看相关的LLVM文章。
LLVM 编译器中 alloc 流程
- alloc -> objc_alloc(Receiver(消息接受者)标记) -> callAlloc -> objc_msgSend(cls, @selector(alloc))
- alloc -> objc_alloc -> callAlloc -> objc_msgSend(cls, @selector(alloc)) -> + (id)alloc -> _objc_rootAlloc 因为第一次循环的时候,Receiver(接受者)已经被标记过,所以第二次循环时不会在发生重绑定,直接走alloc 的方法,这也是callAlloc 为什么断点时候会走两次的原因。