alloc & init & new 源码分析

851 阅读5分钟

在讲解源码分析之前,先看一个案例 从上面的案例不难看出,p1、p2、p3所指向的地址是一样,但是指针的地址又是不一样的,可以猜测出在alloc的时候其实已经把对象创建好了,而init仅仅只是返回alloc创建好的这个对象而已

  • alloc源码分析

    • alloc执行流程图

    • 第一步:_objc_rootAlloc

      调用callAlloc方法
    • 第二步:callAlloc

      // Call [cls alloc] or [cls allocWithZone:nil], with appropriate 
       // shortcutting optimizations.
       static ALWAYS_INLINE id
       callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
       {
       #if __OBJC2__ //if __OBJC2__用于判断objective-c 版本,是不是2.0及以上
      
           /**
            slowpath & fastpath
            是objc源码中定义的两个宏分别是:
            #define fastpath(x) (__builtin_expect(bool(x), 1))
            #define slowpath(x) (__builtin_expect(bool(x), 0))
      
            这个指令是gcc引入的,作用是允许程序员将最有可能执行的分
            支告诉编译器。这个指令的写法为:__builtin_expect(EXP, N)。
            意思是:EXP==N的概率很大。
      
            __builtin_expect() 是GCC (version>=296)提供给程序员使用的,目的是将“分支转移”的信息提供给编译器,这样编译器可以对代码进行优化,以减少指令跳转带来的性能下降。
      
            __builtin_expect((x),1)表示 x 的值为真的可能性更大;
            __builtin_expect((x),0)表示 x 的值为假的可能性更大。
      
            也就是说,使用fastpath(),执行 if 后面的语句的机会更大,使用 slowpath(),执行 else 后面的语句的机会更大。通过这种方式,编译器在编译过程中,会将可能性更大的代码紧跟着前面的代码,从而减少指令跳转带来的性能上的下降。
            */
           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));
       }
      
      
      • fastpath(x) x为真的可能性更大,既执行if里面的语句的机会更大
      • slowpath(x) x为假的可能性更大,既执行else里面的语句的机会更大
      • !cls->ISA()->hasCustomAWZ()) 判断一个类是否有实现allocWithZone方法
    • 第三步:_objc_rootAllocWithZone

      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

       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 {
               // alloc 开辟内存的地方
               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);
           } 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(核心方法)计算内存大小
        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;
        }
        
        if (size < 16) size = 16; 可以看出内存不满16补全至16字节,跟踪断点会执行到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);
            }
        }
        
        • __builtin_constant_p 用于判断一个值是否为编译时常数,如果参数EXP 的值是常数,函数返回 1,否则返回 0(extra不是常数所以走else)

        • align16 16字节对齐算法

          /**
          
          8 + 15 = 23
          
          16 + 7
          
          12
          
          // 8
          // 对象 8 8 8 4 -> 16字节对齐
          
          // name nickname  32
          // isa 8 8 8 = 24 (16) 24 -> 32
          
           0000 0000 0001 0111 23
           1111 1111 1111 0000 15的取反
           0000 0000 0001 0000 16倍数
          
          0000 0000 0000 1111  15
          
          **/
          
          static inline size_t align16(size_t x) {
             return (x + size_t(15)) & ~size_t(15);
          }
          

          内存字节对齐原则:
          没有定义**#pragma pack(n)**之前:

          1. sizeof的最终结果必然是结构内部最大成员的整数倍,不够补齐。结构内部最大成员包含子成员结构体内部的成员,如下例子:
            typedef struct a
             {
                 double e;
                 int g;
                 short l;
             }a;
            
             typedef struct b
             {
                 char c;
                 struct a e;
             }b;
            
             //等价于
            
             typedef struct b
             {
                 char c;
                 double e;
                 int g;
                 short l;
             }b;
            
          2. 结构内部各个成员的首地址必然是自身大小的整数倍。

          定义了**#pragma pack(n)**之后

          1. 整个sizeof的最终结果必然是 min[n,结构内部最大成员] 的整数倍,不够补齐。
          2. 结构内部各个成员的首地址必然是min[n,自身大小]的整数倍。

          所以结构体的属性最好按照占用内存的大小从大往小写(节省空间)

          16字节对齐原因

          1. 通常内存是由一个个字节组成的,cpu在存取数据时,并不是以字节为单位存储,而是以块为单位存取,块的大小为内存存取力度。频繁存取字节未对齐的数据,会极大降低cpu的性能,所以可以通过减少存取次数来降低cpu的开销
          2. 16字节对齐,是由于在一个对象中,第一个属性isa占8字节,当然一个对象肯定还有其他属性,当无属性时,会预留8字节,即16字节对齐,如果有属性,则对象所占用的内存大小是16字节的整数倍,举例:一个对象中有一个int型的属性(占用4字节),还有一个Double类型的属性(占用8字节),不补齐的情况下是占用8(isa)+4(int) + 8(double) = 20,但是需要补齐所以占用32字节,从而也可以得出对象的大小影响因素和属性的多少有关,如果不预留,相当于这个对象的isa和其他对象的isa紧挨着,容易造成访问混乱
          3. 16字节对齐后,可以加快CPU读取速度,同时使访问更安全,不会产生访问混乱的情况
      • calloc(核心方法)申请内存,返回地址指针
        obj = (id)calloc(1, size);
        
        通过cls->instanceSize(extraBytes)方法计算出的大小开辟对应大小的内存并放回对应的指针地址,下图打印可以看出obj是一个地址
      • initInstanceIsa(核心方法)类与isa关联
        inline void 
        objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
        {
            ASSERT(!cls->instancesRequireRawIsa());
            ASSERT(hasCxxDtor == cls->hasCxxDtor());
        
            initIsa(cls, true, hasCxxDtor);
        }
        
        initInstanceIsa方法执行完成之后在打印obj 就可以得出对应对象了如图
  • 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;
    }
    
    可以发现其实init就是返回了self,init是个构造方法(工厂设计)方便对类进行重写
  • new源码分析

    + (id)new {
        return [callAlloc(self, false/*checkNil*/) init];
    }
    
    
    可以看出new = [[xxx alloc] init],慎用没办法实现自定义重写的init方法