阅读 333

iOS底层探索一-对象原理(上)

前言

未来的你,会感谢现在努力的自己。每一份努力都是未来人生道路的财富!

指针地址和内存地址

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的源码可以直观的看的出来。

Jul-13-2021 14-25-02.gif

底层探索的三种方法

1.符号断点

操作步骤

Jun-20-2021 12-02-53.gif

  • xcode 自带调试工具:Ctrl + Step into

image.png

  • 加入符号断点 libobjc.A.dylib objc_alloc:

Jul-13-2021 11-22-33.gif

得到结果

image.png

2.汇编

Jun-20-2021 10-04-34.gif

3.符号断点 确定未知

  • 简单来说:因为我们已经确定会调用alloc ,所有直接对其加符号断点

Jul-13-2021 11-30-37.gif

得到结果

image.png

通过以上三种调试方式,断点断在libobjc.A.dylib。可以在苹果开源网站下载对应的开源源码,对应的配置流程工程网上也有很多,可以下载调试[开车链接],更多的关注alloc的创建流程。

alloc 创建流程

通过流程跟踪,发现最终调用了 _class_createInstanceFromZone

Jul-13-2021 14-43-10.gif

可以用流程图更加清晰的表达

未命名文件 (2).png

_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); //字节对齐
        }
    }
复制代码
  • align16 16字节对齐
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

Aug-26-2021 17-29-35.gif

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 为什么断点时候会走两次的原因。

文章分类
iOS
文章标签