iOS原理探索系列-alloc&init原理探索

默认文件1584500774177

欢迎你们继续阅读iOS原理探索系列篇章(后续陆续完善补充)算法

1.探索背景

在序章中,初步探索了iOS程序启动流程,可是不少东西都涉及系统底层库的加载,以及一系列晦涩难懂的资料,因此为了由浅入深的总结梳理,我这里就从咱们最熟悉的陌生人对象开始。缓存

做为程序猿,没对象不可怕,可怕的是咱们竟然只能本身new一个对象出来,咱们新建对象的方式有两种,以下: bash

-w608
那么到底这两种建立对象的方法在系统底层作了哪些事情,这个就是咱们今天要探索的方向。

2.探索方式

想要知道对象建立方法在底层到底作了哪些事情,咱们就须要去看源代码的实现,正常咱们使用的方式就是跳转到方法定义去查看实现,以下图所示,咱们最多能到NSObject的方法声明,没法去查看具体的源代码了,可是咱们仍是有其余的方式能够去查看源码实现。 app

-w606

3.三种源码调教手段

  • 小断点调试 正常咱们打完断点以下图: 函数

    -w873
    下面咱们按住control键,再看断点,能够看到出现了变化以下图:
    -w931
    接下来咱们按住control的同时,点击中间的下箭头,能够看到程序来到汇编:
    -w737
    咱们能够知道,在咱们调用alloc方法时,系统底层调用了objc_alloc,因此咱们能够添加符号断点objc_alloc查看底层库
    -w450
    -w930
    能够看到alloc底层库是libobjc.A.dylib库。

  • 符号断点调试 一样咱们能够直接添加符号断点alloc来查看底层库实现,以下图: 源码分析

    -w533
    而后咱们就能够看到出现了很是多的alloc的调用
    -w740
    -w1072
    一样能够看到底层是libobjc.A.dylib库实现。

  • 开启Debug Workflow模式 经过前两种方式咱们能够看到,底层都是汇编,由于OC是高级语言,最终能被机器识别的仍是汇编语言,因此咱们能够直接开启查看汇编代码模式,以下图: post

    -w1034
    开启后,能够看到以下效果,能够看到汇编代码中一样出现了objc_alloc
    -w1259
    而后咱们按照方式一按住control+下箭头,继续进入到objc_alloc的断点,继续往下走,看到以下,一样可以看到objc_alloc
    -w1102

4. libobjc.A.dylib源码分析

Apple其实开源了不少源码给咱们,libobjc也是其中之一,想要直接查看源码实现能够按照以下操做:优化

这里我也下载编译了最新的779.1,而后咱们能够查看源代码,可是咱们能够看到源代码很是多,大部分人一开始看确定很懵逼,因此咱们不用所有文件去看,只须要看咱们的目标代码便可。 ui

-w763

就如同咱们在断点调试中已经知道, alloc方法最终底层都是到NSObjectalloc方法,因此咱们能够直接找到NSObject的头文件,查看alloc方法的实现,也能够全局搜索alloc {,以下图: this

-w936

接下来咱们就能够逐步去查看alloc的底层实现原理了。

5.alloc流程分析

  • 咱们查看_objc_rootAlloc的具体实现能够看到以下代码,其实从注释能够很清晰的看到,全部建立类的初始化方法最终都是调用了callAlloc函数。

    -w1018

  • 接下里咱们来具体分析下callAlloc函数到底作了作什么,以下,因为咱们目前基本上都是在objctive-c 2.0环境下,因此只须要看__OBJC2__这个判断下的代码便可。

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())) {
        // 这是判断一个类是否有自定义的 +allocWithZone 实现。
        //hasCustomAWZ : hasCustomAllocWithZone
        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));
}
NEVER_INLINE
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);
}
复制代码
  • 最终来到实现函数_class_createInstanceFromZone,在实现函数中咱们能够看到,咱们先为obj申请分配了内存空间,而且绑定地址
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());
    //hasCxxCtor() 是判断当前 class 或者 superclass 是否有 .cxx_construct 构造方法的实现。
    bool hasCxxCtor = cxxConstruct && cls->hasCxxCtor();
    //hasCxxDtor() 是判断判断当前 class 或者 superclass 是否有 .cxx_destruct 析构方法的实现。
    bool hasCxxDtor = cls->hasCxxDtor();
    //具体标记某个类是否支持优化的isa.
    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 {
        /**
        *  void    *calloc(size_t __count, size_t __size)
        *  在内存的动态存储区中分配 __count 个长度为 __size 的连续空间
        */
        obj = (id)calloc(1, size);
    }
    if (slowpath(!obj)) {
        if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {
            return _objc_callBadAllocHandler(cls);
        }
        return nil;
    }

    if (!zone && fast) {
        // 对象isa的初始化 以及绑定 内存空间
        obj->initInstanceIsa(cls, hasCxxDtor);
    } 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);
} 
复制代码

6.alloc原理分析

  • 关于_objc_rootAlloc,咱们经过代码调试发现,这里alloc点击进入的为_objc_rootAlloc其实咱们断点后发现进入的是objc_alloc。(关于这个缘由,我后面会单独分析下,这里暂时不展开,由于最终你们调用的都是callAlloc这个函数)
id
objc_alloc(Class cls)
{
    return callAlloc(cls, true/*checkNil*/, false/*allocWithZone*/);
}

//  这里传入了一个`Class`参数
typedef struct objc_class *Class;

// 咱们看下objc_class的结构
struct objc_class : objc_object {
    // Class 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() const {
        return bits.data();
    }
...
// 再看看objc_object的结构
struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
} OBJC2_UNAVAILABLE;
复制代码

因为咱们新建的是一个objc_class类,objc_class又继承于objc_classobjc_class存储了isa的一个Class结构体,指向当前是什么类。

  • 关于callAlloc函数

    • if (slowpath(checkNil && !cls)) return nil;断定当前类是否存在
    //__builtin_expect 经常使用于 if-else 的判断为了优化判断的速度。
    //bool(x) 为真的可能性更大,if 下的代码执行的可能性更高
    #define fastpath(x) (__builtin_expect(bool(x), 1))
    //bool(x) 为假的可能性更大,else 下的代码执行的可能性更高
    #define slowpath(x) (__builtin_expect(bool(x), 0))
    复制代码
    • if (fastpath(!cls->ISA()->hasCustomAWZ()))的判断
      hasCustomAWZ其实就是 hasCustomAllocWithZone的意思,因此这里用来断定继承了NSObject/NSProxy的类才会进入这里。

    • 关于_class_createInstanceFromZone函数 能够看到_objc_rootAllocWithZone里面最终调用的就是_class_createInstanceFromZone

    • hasCxxCtor() bool hasCxxCtor = cls->hasCxxCtor(); hasCxxCtor()是判断当前class或者 superclass是否有.cxx_construct构造方法的实现。

    • hasCxxDtor() bool hasCxxDtor = cls->hasCxxDtor(); hasCxxDtor()是判断判断当前class或者superclass是否有.cxx_destruct析构方法的实现。

    • canAllocNonpointer() bool fast = cls->canAllocNonpointer();具体标记某个类是否支持优化的isa.

    • instanceSize()

    size_t size = cls->instanceSize(extraBytes); 获取类的大小(传入额外字节的大小)
    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;
    }
    复制代码
    • 内存对齐(alignedInstanceSize()) 在unalignedInstanceSize方法中 data()->ro->instanceSize获取类所占用空间的大小,实际上是在MacOdata段的ro中的获取类所占用的大小。

      关于字节对齐OC是8字节对齐,在 word_align这个方法中计算了字节对齐。

    static inline uint32_t word_align(uint32_t x) {
        //字节对齐
        return (x + WORD_MASK) & ~WORD_MASK;
    }
    复制代码
    • 关于对齐算法,能够参考下面的示例说明:以下计算过程就能看到 OC 采用了 8 字节对齐,能够看到咱们建立一个空类的大小是8字节(只有一个ISA指针),可是苹果对于类作了限制,最小须要16字节。
    假设传入的参数: x = 9
    x + WORD_MASK = 9 + 7 = 16
    WORD_MASK 二进制 :0000 0111 = 7(4+2+1)
    ~WORD_MASK : 1111 1000
    16二进制为  : 0001 0000
    1111 1000
    0001 0000
    ·········
    0001 0000 = 16
    因此 x = 16(原始值:9) 也就是 8的倍数对齐,即 8 字节对齐
    也能够用相似位运算来实现此算法
    好比 (x >> 3) <<3 也能够实现对等功能
    复制代码
    • initInstanceIsa() 上一步咱们获取到了obj的内存空间,接下来使用 obj->initInstanceIsa(cls, hasCxxDtor) 绑定isa指针,说明这块内存空间是为谁开辟的。 而后返回obj

7.关于newinit

  • init方法,以下,能够看到其实init方法什么都没作,只是返回了对象自己
// Replaced by CF (throws an NSException)
+ (id)init {
    return (id)self;
}
- (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;
}
复制代码
  • new其实最终调用了objc_opt_new,本质上就至关于[[cls alloc] init]
id
objc_opt_new(Class cls)
{
#if __OBJC2__
    if (fastpath(cls && !cls->ISA()->hasCustomCore())) {
        return [callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/) init];
    }
#endif
    return ((id(*)(id, SEL))objc_msgSend)(cls, @selector(new));
}
复制代码

8. 流程图总结

alloc流程分析

以上就是对alloc的初步总结,中间有些细节问题可能没有彻底覆盖到,好比对象的析构函数,构造函数等,后面会在类的初始化分析中详细分析,敬请期待~