iOS底层探索二(OC 中 alloc 方法 初探)

前言      

     相关文章:   

      iOS底层探索一(底层探索方法)

      iOS底层探索三(内存对齐与calloc分析)

      iOS底层探索四(isa初探-联合体,位域,内存优化)

      iOS底层探索五(isa与类的关系)  

iOS底层探索六(类的分析上)

iOS底层探索七(类的分析下)

iOS底层探索八(方法本质上)

iOS底层探索九(方法的本质下objc_msgSend慢速及方法转发初探)

iOS底层探索十(方法的本质下-消息转发流程)

开发中我们经常会用到XZPerson *p= [[XZPerson alloc]init],我们只知道,这样我们就新建了一个对象,可以直接使用这个对象,可以对这个对象进行赋值使用,但是我们没有关心过alloc方法底层到底是通过什么方式进行实现的;今天我准备对alloc方法进行一次尝试性底层挖掘;首先我们需要先准备一份可编译的objc4_752代码,可以直接进行下载探索;

首先,先上一张我探索完成的alloc流程图,供大家进行参考

然后我们直接使用能运行 objc-752 源码后,在 main.m 中写入如下代码就开始了 alloc 的探索。

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        XZPerson *object = [XZPerson alloc];
        NSLog(@"Hello, World! %@",object);

    }
    return 0;
}

打断点直接这里 alloc 点击进入的为 _objc_rootAlloc 其实我们断点后发现进入的是 objc_alloc

这里我们稍作解释:我们可以看到源码中有这么一个方法fixupMessageRef,这里应该是对进行符号绑定将alloc绑定为objc_alloc方法,具体是通过LLVM期进行符号绑定,我们这篇文章先不进行详细介绍;

fixupMessageRef(message_ref_t *msg)
{    
    msg->sel = sel_registerName((const char *)msg->sel);

    if (msg->imp == &objc_msgSend_fixup) { 
        if (msg->sel == SEL_alloc) {
            msg->imp = (IMP)&objc_alloc;
        } else if (msg->sel == SEL_allocWithZone) {
            msg->imp = (IMP)&objc_allocWithZone;
        } else if (msg->sel == SEL_retain) {
            msg->imp = (IMP)&objc_retain;
        } else if (msg->sel == SEL_release) {
            msg->imp = (IMP)&objc_release;
        } else if (msg->sel == SEL_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
}

1.首先我们可以看到进入的是 alloc 内部调用_objc_rootAlloc方法绩效调用callAlloc方法

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

id
_objc_rootAlloc(Class cls)
{
    return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}

这里我们可以看到进入_objcrootAlloc方法里面有参数为Class的一个参数

Class 类底层其实是objc_class类结构体
typedef struct objc_class *Class;

objc_class结构体中
struct objc_class : objc_object {
   // Class ISA;         //isa指针
    Class superclass;  //父类
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags

    class_rw_t *data() { 
        return bits.data();
    }
    //...下面还有很多方法就不在这里贴了,可以自行下代码进行查看

objc_class 又继承于 objc_object ,objc_object 存储了 isa 的一个 Class 结构体

struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class _Nullable super_class                              OBJC2_UNAVAILABLE;
    const char * _Nonnull name                               OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;

这里如果有使用过RunTime的同学就可以看到一些熟悉的东西 ivars, methodLists ,protocols,这个后期文章我们再进行赘述,此篇文章先进行稍微引入

2.当进入callAlloc方法中

这里我都有备注判断的原因,如有不对还请看到的各位,评论留言我们可以一起探讨,这里如果我们自己打断点的话,可以看到大部分会直接走到class_createInstance方法中;

callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
    /**
     fastpath(x)表示x很可能不为0,希望编译器进行优化;
    slowpath(x)表示x很可能为0,希望编译器进行优化——这里表示cls大概率是有值的,编译器可以不用每次都读取 return nil 指令
     */
    if (slowpath(checkNil && !cls)) return nil;

#if __OBJC2__
    /**
     实际意义是hasCustomAllocWithZone——这里表示有没有alloc / allocWithZone的实现(只有不是继承NSObject/NSProxy的类才为true)
     */
    if (fastpath(!cls->ISA()->hasCustomAWZ())) {
        // No alloc/allocWithZone implementation. Go straight to the allocator.
        // fixme store hasCustomAWZ in the non-meta class and 
        // add it to canAllocFast's summary
        /**

         内部调用了bits.canAllocFast后分为2种情况
        if(FAST_ALLOC){
          }else{
             默认为false,
         }
         当我们查看FAST_ALLOC宏时,发现它的上方有个else 1 之后才是else中定义这个宏,
        也就是说这个宏一直是不存在的
        可以自行点击进入查看
         */
        if (fastpath(cls->canAllocFast())) {
            // No ctors, raw isa, etc. Go straight to the metal.
            bool dtor = cls->hasCxxDtor();
            id obj = (id)calloc(1, cls->bits.fastInstanceSize());
            if (slowpath(!obj)) return callBadAllocHandler(cls);
            obj->initInstanceIsa(cls, dtor);
            return obj;
        }
        else {
            // Has ctor or raw isa or something. Use the slower path.
            id obj = class_createInstance(cls, 0);
            if (slowpath(!obj)) return callBadAllocHandler(cls);
            return obj;
        }
    }
#endif

    // No shortcuts available.
    if (allocWithZone) return [cls allocWithZone:nil];
    return [cls alloc];
}

3.class_createInstance方法中,跟踪class_createInstance(cls, 0)方法,其内部调用了_class_createInstanceFromZone方法,并在其中进行size计算,内存申请,以及isa初始化

class_createInstance(Class cls, size_t extraBytes)
{
    return _class_createInstanceFromZone(cls, extraBytes, nil);
}

_class_createInstanceFromZone这个方法是核心方法,进行了开辟内存和关联对象

_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone, 
                              bool cxxConstruct = true, 
                              size_t *outAllocatedSize = nil)
{
    if (!cls) return nil;

    assert(cls->isRealized());

    // Read class's info bits all at once for performance
    bool hasCxxCtor = cls->hasCxxCtor();
    bool hasCxxDtor = cls->hasCxxDtor();
    bool fast = cls->canAllocNonpointer();
   // 获取需要开辟的内存大小
    size_t size = cls->instanceSize(extraBytes);
    if (outAllocatedSize) *outAllocatedSize = size;

    id obj;
    if (!zone  &&  fast) {
        //这里是真正的创建类的方法但是这个calloc源码是在malloc中
        obj = (id)calloc(1, size);
        if (!obj) return nil;
        //将内存指向给objc进行关联
        obj->initInstanceIsa(cls, hasCxxDtor);
    } 
    else {
        if (zone) {
            obj = (id)malloc_zone_calloc ((malloc_zone_t *)zone, 1, size);
        } else {
            obj = (id)calloc(1, size);
        }
        if (!obj) return nil;

        // Use raw pointer isa on the assumption that they might be 
        // doing something weird with the zone or RR.
        obj->initIsa(cls);
    }

    if (cxxConstruct && hasCxxCtor) {
        obj = _objc_constructOrFree(obj, cls);
    }

    return obj;
}

其中cls->instanceSize(extraBytes)此方法为计算应该开辟多大内存方法,计算出size,
其中64位系统下,对象大小采用8字节对齐,但是实际申请的内存最低为16字节,也就是说系统分配内存按照16字节对齐分配

 size_t instanceSize(size_t extraBytes) {
        size_t size = alignedInstanceSize() + extraBytes;
        // CF requires all objects be at least 16 bytes.
        if (size < 16) size = 16;//最小返回16字节
        return size;
  }

 uint32_t alignedInstanceSize() {
        //字节对齐
        return word_align(unalignedInstanceSize());
  }

#   define WORD_MASK 7UL  //这个宏标示数字7
static inline uint32_t word_align(uint32_t x) {
    //WORD_MASK  7
    //7+8 = 15
    //0000 0111 2进制表示7
    //0000 1111 2进制表示15
    //1111 1000 2进制表示~7
    //0000 1000 15 & ~7 值 16
    //这里主要就是做8进制对齐,这里相当于8倍数
    //x + 7
    //x=1  8
    //x=2  16
    return (x + WORD_MASK) & ~WORD_MASK;
}

字节对齐核心算法:

例如传值进来为7 已知 WORD_MASK为7

   //WORD_MASK  7
    7+8 = 15
    0000 0111        2进制表示7
    0000 1111         2进制表示15
    1111 1000         2进制表示~7

---------------------------------
    /0000 1000    (15 & ~7 ) 值 16

  这里主要就是做8进制对齐,这里相当于8倍数,当然这里的WORD_MASK如果是15那么这个就是16字节对齐

  根据不同的条件,使用calloc或者malloc_zone_calloc进行内存申请,并且初始化isa指针,至此size大小的对象obj已经申请完成,并且返回;

4.这就OC中alloc底层的调用方式,我们都看了alloc的源码了,顺便看下init方法吧;

- (id)init {
    return _objc_rootInit(self);
}

id
_objc_rootInit(id obj)
{
    // In practice, it will be hard to rely on this function.
    // Many classes do not properly chain -init calls.
    return obj;
}

我们可以看出来,在init方法中苹果开发人员没有做任何东西,只是将对象直接返回,说明这只是苹果开发人员为我们提供的一个工厂方法,让我们方便进行重写;

5.顺便把New方法也看下

+ (id)new {
    return [callAlloc(self, false/*checkNil*/) init];
}

仔细看过alloc源码的话,这里就很清晰了,callAlloc不就是调用了下alloc方法的底层么,顺便也调用了下init,这就是说,new方法底层也是调用alloc的,

总结

这是个人的实际追踪源码得出的一些结论,源码中还有一些分支代码可以自行查看initInstanceIsa下篇文章在进行分析,如果有错误的地方还请指正,大家一起讨论,开发水平一般,还望大家体谅,欢迎大家点赞,关注我的CSDN,我会定期做一些技术分享!

写给自己:

研究源码枯燥的,但是面对源码不用害怕,一步步把它拆分开来研究,多看看官方给的注释/Github大神的注释,慢慢来,总有一自己也会厉害的。

原创文章 88 获赞 21 访问量 2万+

猜你喜欢

转载自blog.csdn.net/ZhaiAlan/article/details/104632626