美文网首页
OC alloc 底层探索

OC alloc 底层探索

作者: HotPotCat | 来源:发表于2021-06-07 15:28 被阅读0次

    一、alloc对象的指针地址和内存

    有如下代码:

    //alloc后分配了内存,有了指针。
    //init所指内存地址一样,init没有对指针进行操作。
    HPObject *hp1 = [HPObject alloc];
    HPObject *hp2 = [hp1 init];
    HPObject *hp3 =  [hp1 init];
    NSLog(@"%@-%p",hp1,hp1);
    NSLog(@"%@-%p",hp2,hp2);
    NSLog(@"%@-%p",hp3,hp3);
    

    输出:

    <HPObject: 0x600000f84330>-0x600000f84330
    <HPObject: 0x600000f84330>-0x600000f84330
    <HPObject: 0x600000f84330>-0x600000f84330
    

    说明alloc后进行了内存分配有了指针,而init后所指内存地址一致,所以init没有对指针进行操作。
    修改NSLog内容如下:

    NSLog(@"%@-%p &p:%p",hp1,hp1,&hp1);
    NSLog(@"%@-%p &p:%p",hp2,hp2,&hp2);
    NSLog(@"%@-%p &p:%p",hp3,hp3,&hp3);
    

    输出:

    <HPObject: 0x600000e7c2c0>-0x600000e7c2c0 &p:0x7ffeefbf40d8
    <HPObject: 0x600000e7c2c0>-0x600000e7c2c0 &p:0x7ffeefbf40d0
    <HPObject: 0x600000e7c2c0>-0x600000e7c2c0 &p:0x7ffeefbf40c8
    

    这就说明hp1hp2hp3都指向堆空间的一块区域。而3个指针本身是在栈中连续开辟的空间,从高地址->低地址。
    那么alloc是怎么开辟的内存空间呢?

    二、底层探索思路

    1. 断点结合Step into instruction进入调用堆栈找到关键函数:

      image.png
      这里以alloc为例:
      image.png
      找到了objc_alloc关键韩素,然后对它下符号断点:
      image.png
      找到了最中调用的是libobjc.A.dylibobjc_alloc:`。
    2. 下断点后通过汇编查看调用流程Debug->Debug workflow->Always Show Disassembly

      image.png
      可以直接跟进去看调用情况。
    3. 通过已知符号断点确定未知符号。
      直接alloc下符号断点跟踪:

      image.png

    三、alloc源码分析

    通过上面的分析已经能确定allocobjc框架中,正好苹果开源了这块代码,源码:objc源码地址:Source Browser
    最好是自己能编译一份能跑通的源码(也可以直接github上找别人编译好的)。当然也可以根据源码下符号断点跟踪调试。由于objc4-824目前下载不了,这里以objc4-824.2为例进行调试。

    HPObject定义如下:

    @interface HPObject : NSObject
    
    @property (nonatomic, copy) NSString *name;
    @property (nonatomic, assign) int age;
    
    @end
    

    3.1 alloc

    直接搜索alloc函数的定义发现在NSObject.mm 2543,通过断点调试类。
    调用alloc会首先调用objc_alloc:

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

    callAlloc会走到调用alloc分支。

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

    alloc直接调用了_objc_rootAlloc

    id
    _objc_rootAlloc(Class cls)
    {
        return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
    }
    
    • _objc_rootAlloc传递参数checkNilfalseallocWithZonetrue直接调用了callAlloc
    • 在调用objc_alloc的时候传递的checkNiltrueallocWithZonefalse

    这里没什么好说的只是方法的一些封装,具体实现要看callAlloc

    ⚠️:明明有alloc方法为什么先调用的是objc_alloc?文章后面会有分析

    3.2 callAlloc

    static ALWAYS_INLINE id
    callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
    {
    #if __OBJC2__
        //表示值为假的可能性更大。即执行else里面语句的机会更大
        if (slowpath(checkNil && !cls)) return nil;
        //hasCustomAWZ方法判断是否实现自定义的allocWithZone方法,如果没有实现就调用系统默认的allocWithZone方法。
        //表示值为真的可能性更大;即执行if里面语句的机会更大
        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));
    }
    

    slowpath:表示值为假的可能性更大。即执行else里面语句的机会更大。
    fastpath:表示值为真的可能性更大;即执行if里面语句的机会更大。
    OBJC2:是因为有两个版本。Legacy版本(早期版本,对应Objective-C 1.0) 和 Modern版本(现行版本Objective-C 2.0)。

    • 在首次调用的时候会走alloc分支进入到alloc逻辑。
    • hasCustomAWZ意思是hasCustomAllocWithZone有没有自定义实现AllocWithZone。没有实现就走(这里进行了取反)_objc_rootAllocWithZone,实现了走allocWithZone:
    • 第二次调用直接走callAlloc的其它分支不会调用到alloc

    ⚠️:自己实现一个类的allocWithZone alloc分支就每次都被调用了。

    3.3 _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);
    }
    

    _objc_rootAllocWithZone直接调用了_class_createInstanceFromZone

    3.4 allocWithZone

    // Replaced by ObjectAlloc
    + (id)allocWithZone:(struct _NSZone *)zone {
        return _objc_rootAllocWithZone(self, (malloc_zone_t *)zone);
    }
    

    _objc_rootAllocWithZone直接调用了_objc_rootAllocWithZone,与上面的3.3中的逻辑汇合了。

    3.5 _class_createInstanceFromZone

    最终会调用_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
        //判断当前class或者superclass是否有.cxx_construct构造方法的实现
        bool hasCxxCtor = cxxConstruct && cls->hasCxxCtor();
        //判断当前class或者superclass是否有.cxx_destruct析构方法的实现
        bool hasCxxDtor = cls->hasCxxDtor();
        //标记类是否支持优化的isa
        bool fast = cls->canAllocNonpointer();
        size_t size;
        //通过内存对齐得到实例大小,extraBytes是由对象所拥有的实例变量决定的。
        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;
        }
        //初始化实例isa指针
        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计算空间大小。
    • 根据zone是否有值调用malloc_zone_calloccalloc进行内存分配。
      calloc之前分配的obj是一块脏内存,执行calloc后才会真正分配内存。执行前后内存地址发生了变化。
      image.png
      并且可以看到po只打印出了地址没有打印出类型,这也就说明目前obj没有绑定到类。需要通过isa绑定。
    • 根据!zone && fast分别调用initInstanceIsainitIsa进行isa实例化。
      image.png
      执行完initInstanceIsa后再次打印就有类型了。
    • 根据是否有hasCxxCtor分别返回obj和调用object_cxxConstructFromClass

    3.6 instanceSize 申请内存

    在这个函数中调用了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;
    }
    
    
    • 没有缓存的话会调用alignedInstanceSize,如果最终的size < 16会返回16
    • 有缓存则调用fastInstanceSize
    • 正常情况下缓存是在_read_images的时候生成的。所以这里一般会走fastInstanceSize分支。

    3.6.1 alignedInstanceSize

    #ifdef __LP64__
    #   define WORD_MASK 7UL
    #else
    #   define WORD_MASK 3UL
    
    uint32_t alignedInstanceSize() const {
        return word_align(unalignedInstanceSize());
    }
    
    static inline uint32_t word_align(uint32_t x) {
        return (x + WORD_MASK) & ~WORD_MASK;
    }
    
    uint32_t unalignedInstanceSize() const {
        ASSERT(isRealized());
        return data()->ro()->instanceSize;
    }
    
    • xunalignedInstanceSize获取。读取的是data()->ro()->instanceSize实例变量的大小。由ivars决定。这里为8,因为默认有个isaisaClassClassobjc_class struct *类型。
    @interface NSObject <NSObject> {
    #pragma clang diagnostic push
    #pragma clang diagnostic ignored "-Wobjc-interface-ivars"
     Class isa  OBJC_ISA_AVAILABILITY;
    #pragma clang diagnostic pop
    }
    
    • 字节对齐算法为:(x + WORD_MASK) & ~WORD_MASKWORD_MASK 64位下为732位下为3
      那么对于HPObject对象计算方法如下:
      根据公式可得1:(8 + 7) & ~7 等价于 (8 + 7) >>3 << 3
      根据1可得2:15 & ~7
      转换为二进制:0000 1111 & ~0000 0111 = 0000 1111 & 1111 1000
      计算可得:00001000 = 8
      所以alignedInstanceSize计算就是以8字节对齐取8的倍数(算法中是往下取,对于内存分配来讲是往上取)。

    那么为什么以8字节对齐,最后最小分配16呢?
    分配16是为了做容错处理。以8字节对齐(选择8字节是因为8字节类型是最常用最多的)是以空间换取时间,提高CPU读取速度。当然这过程中会做一定的优化。

    3.6.2 fastInstanceSize

    bool hasFastInstanceSize(size_t extra) const
    {
        if (__builtin_constant_p(extra) && extra == 0) {
            return _flags & FAST_CACHE_ALLOC_MASK16;
        }
        return _flags & FAST_CACHE_ALLOC_MASK;
    }
    
    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);
        }
    }
    
    • fastInstanceSize中会调用align16,实现如下(16字节对齐):
    static inline size_t align16(size_t x) {
        return (x + size_t(15)) & ~size_t(15);
    }
    

    3.6.3 setInstanceSize

    setInstanceSize代码如下:

    void setInstanceSize(uint32_t newSize) {
        ASSERT(isRealized());
        ASSERT(data()->flags & RW_REALIZING);
        auto ro = data()->ro();
        if (newSize != ro->instanceSize) {
            ASSERT(data()->flags & RW_COPIED_RO);
            *const_cast<uint32_t *>(&ro->instanceSize) = newSize;
        }
        cache.setFastInstanceSize(newSize);
    }
    

    size变化只会走会更新在缓存中。那么调用setInstanceSize的地方如下:

    • realizeClassWithoutSwift:类加载的时候计算。这里包括懒加载和非懒加载。这里会调用方法,根据类的实例变量进行size计算。这里是在_read_images的时候调用。
    • class_addIvar:动态添加属性的时候会重新计算实例大小。
    • objc_initializeClassPair_internal:动态添加类相关的初始化。

    instanceSize对于HPObject而言分配内存大小应该为8(isa) + 8(name)+4(age)= 20根据内存对齐应该分配24字节。

    image.png
    而根据调试instanceSize最终返回了32。为什么?参考iOS内存对齐

    3.7 initInstanceIsa

    inline void 
    objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
    {
        ASSERT(!cls->instancesRequireRawIsa());
        ASSERT(hasCxxDtor == cls->hasCxxDtor());
    
        initIsa(cls, true, hasCxxDtor);
    }
    

    initInstanceIsa最终会调用initIsainitIsa最后会对isa进行绑定:

    inline void 
    objc_object::initIsa(Class cls, bool nonpointer, UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT bool hasCxxDtor)
    { 
        ASSERT(!isTaggedPointer()); 
        
        isa_t newisa(0);
    
        if (!nonpointer) {
            newisa.setClass(cls, this);
        } else {
            ASSERT(!DisableNonpointerIsa);
            ASSERT(!cls->instancesRequireRawIsa());
    
    
    #if SUPPORT_INDEXED_ISA
            ASSERT(cls->classArrayIndex() > 0);
            newisa.bits = ISA_INDEX_MAGIC_VALUE;
            // isa.magic is part of ISA_MAGIC_VALUE
            // isa.nonpointer is part of ISA_MAGIC_VALUE
            newisa.has_cxx_dtor = hasCxxDtor;
            newisa.indexcls = (uintptr_t)cls->classArrayIndex();
    #else
            newisa.bits = ISA_MAGIC_VALUE;
            // isa.magic is part of ISA_MAGIC_VALUE
            // isa.nonpointer is part of ISA_MAGIC_VALUE
    #   if ISA_HAS_CXX_DTOR_BIT
            newisa.has_cxx_dtor = hasCxxDtor;
    #   endif
            newisa.setClass(cls, this);
    #endif
            newisa.extra_rc = 1;
        }
    
        // This write must be performed in a single store in some cases
        // (for example when realizing a class because other threads
        // may simultaneously try to use the class).
        // fixme use atomics here to guarantee single-store and to
        // guarantee memory order w.r.t. the class index table
        // ...but not too atomic because we don't want to hurt instantiation
        isa = newisa;
    }
    
    • isa_t是一个union
    • nonpointer表示是否进行指针优化。不优化直接走setClass逻辑,优化走else逻辑。

    3.8 alloc调用流程图

    alloc调用流程

    四、llvm优化alloc

    为什么调用alloc最终调用了objc_alloc

    4.1 objc源码中探索分析

    在源码中我们点击alloc会进入到+ (id)alloc方法,但是在实际调试中却是先调用的objc_alloc,系统是怎么做到的呢?

    image.png
    在源码中可以看到上图中的官方注释。所以这里难道是SELIMP进行了交换?
    尝试去源码中找哪里进行了objc_alloc替换,最终在void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)方法中找到了如下代码:
    fixupMessageRef(refs+i);
    

    fixupMessageRef实现中有如下核心代码:

    image.png
    可以看到在这个方法中进行了imp的重新绑定将alloc绑定到了objc_alloc上面。当然retainrelease等都进行了同样的操作。
    既然在_read_images中出现问题的时候尝试进行fixup,那么意味着正常情况下在_read_images之前llvm的编译阶段就完成了绑定。

    4.2 llvm源码探索分析

    那么直接在llvm中搜索objc_alloc,在ObjCRuntime.h中发现了如下注释:

      /// When this method returns true, Clang will turn non-super message sends of
      /// certain selectors into calls to the corresponding entrypoint:
      ///   alloc => objc_alloc
      ///   allocWithZone:nil => objc_allocWithZone
    

    这说明方向没有错,最中在CGObjC.cpp中找到了如下代码:

      case OMF_alloc:
        if (isClassMessage &&
            Runtime.shouldUseRuntimeFunctionsForAlloc() &&
            ResultType->isObjCObjectPointerType()) {
            // [Foo alloc] -> objc_alloc(Foo) or
            // [self alloc] -> objc_alloc(self)
            if (Sel.isUnarySelector() && Sel.getNameForSlot(0) == "alloc")
              return CGF.EmitObjCAlloc(Receiver, CGF.ConvertType(ResultType));
            // [Foo allocWithZone:nil] -> objc_allocWithZone(Foo) or
            // [self allocWithZone:nil] -> objc_allocWithZone(self)
            if (Sel.isKeywordSelector() && Sel.getNumArgs() == 1 &&
                Args.size() == 1 && Args.front().getType()->isPointerType() &&
                Sel.getNameForSlot(0) == "allocWithZone") {
              const llvm::Value* arg = Args.front().getKnownRValue().getScalarVal();
              if (isa<llvm::ConstantPointerNull>(arg))
                return CGF.EmitObjCAllocWithZone(Receiver,
                                                 CGF.ConvertType(ResultType));
              return None;
            }
        }
        break;
    

    可以看出来alloc最后执行到了objc_alloc。那么具体的实现就要看CGF.EmitObjCAlloc方法:

    llvm::Value *CodeGenFunction::EmitObjCAlloc(llvm::Value *value,
                                                llvm::Type *resultType) {
      return emitObjCValueOperation(*this, value, resultType,
                                    CGM.getObjCEntrypoints().objc_alloc,
                                    "objc_alloc");
    }
    
    llvm::Value *CodeGenFunction::EmitObjCAllocWithZone(llvm::Value *value,
                                                        llvm::Type *resultType) {
      return emitObjCValueOperation(*this, value, resultType,
                                    CGM.getObjCEntrypoints().objc_allocWithZone,
                                    "objc_allocWithZone");
    }
    
    llvm::Value *CodeGenFunction::EmitObjCAllocInit(llvm::Value *value,
                                                    llvm::Type *resultType) {
      return emitObjCValueOperation(*this, value, resultType,
                                    CGM.getObjCEntrypoints().objc_alloc_init,
                                    "objc_alloc_init");
    }
    

    这里可以看到alloc以及objc_alloc_init相关的逻辑。这样就实现了绑定。那么系统是怎么走到OMF_alloc的逻辑的呢?
    通过发送消息走到这块流程:

    CodeGen::RValue CGObjCRuntime::GeneratePossiblySpecializedMessageSend(
        CodeGenFunction &CGF, ReturnValueSlot Return, QualType ResultType,
        Selector Sel, llvm::Value *Receiver, const CallArgList &Args,
        const ObjCInterfaceDecl *OID, const ObjCMethodDecl *Method,
        bool isClassMessage) {
        //尝试发送消息
      if (Optional<llvm::Value *> SpecializedResult =
              tryGenerateSpecializedMessageSend(CGF, ResultType, Receiver, Args,
                                                Sel, Method, isClassMessage)) {
        return RValue::get(SpecializedResult.getValue());
      }
      return GenerateMessageSend(CGF, Return, ResultType, Sel, Receiver, Args, OID,
                                 Method);
    }
    
    • 苹果对alloc等特殊函数做了hook,会先走底层的标记emitObjCValueOperation。最终再走到alloc等函数。
    • 第一次会走tryGenerateSpecializedMessageSend分支,第二次就走GenerateMessageSend分支了。
      • 也就是第一次alloc调用了objc_alloc,第二次alloc后就没有调用objc_alloc走了正常的objc_msgSendalloc-> objc_alloc -> callAlloc -> alloc -> _objc_rootAlloc -> callAlloc。这也就是callAlloc走两次的原因。
      • 再创建个对象调用流程就变成了:alloc -> objc_alloc -> callAlloc

    五、内存分配优化

    HPObject *hpObject = [HPObject alloc];
    NSLog(@"%@:",hpObject);
    

    对于hpObject我们查看它的内存数据如下:

    (lldb) x hpObject
    0x6000030cc2e0: c8 74 e6 0e 01 00 00 00 00 00 00 00 00 00 00 00  .t..............
    0x6000030cc2f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
    (lldb) p 0x000000010ee674c8
    (long) $4 = 4544951496
    

    可以打印的isa4544951496并不是HPObject。因为这里要&mask,在源码中有一个&mask结构。arm64`定义如下:

    #   if __has_feature(ptrauth_calls) || TARGET_OS_SIMULATOR
    #     define ISA_MASK        0x007ffffffffffff8ULL
    #   else
    #     define ISA_MASK        0x0000000ffffffff8ULL
    

    这样计算后就得到isa了:

    (lldb) po 0x000000010ee674c8 & 0x007ffffffffffff8
    HPObject
    

    HPObjetc添加属性并赋值,修改逻辑如下:

    @interface HPObject : NSObject
    
    @property (nonatomic, copy) NSString *name;
    @property (nonatomic, assign) int age;
    @property (nonatomic, assign) double height;
    @property (nonatomic, assign) BOOL marry;
    
    @end
    

    调用:

        HPObject *hpObject = [HPObject alloc];
        hpObject.name = @"HotpotCat";
        hpObject.age = 18;
        hpObject.height = 180.0;
        hpObject.marry = YES;
    
    image.png
    这个时候发现agemarry存在了isa后面存在了一起。
    那么多增加几个BOOL属性呢?
    @interface HPObject : NSObject
    
    @property (nonatomic, copy) NSString *name;
    @property (nonatomic, assign) int age;
    @property (nonatomic, assign) double height;
    @property (nonatomic, assign) BOOL marry;
    @property (nonatomic, assign) BOOL flag1;
    @property (nonatomic, assign) BOOL flag2;
    @property (nonatomic, assign) BOOL flag3;
    
    @end
    

    BOOL属性都设置为YES查看内存地址:

    image.png

    发现所有BOOL值都放在了一起。那么再增加一个呢?

    image.png

    可以看到int类型的age单独存放了,5bool值放在了一起。这也就是内存分配做的优化。

    六、init源码探索

    既然alloc已经完成了内存分配和isa与类的关联那么init中做了什么呢?

    init
    init源码定义如下:

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

    _objc_rootInit

    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中调用了_objc_rootInit,而_objc_rootInit直接返回obj没有做任何事情。就是给子类用来重写的,提供接口便于扩展。所以如果没有重写init方法,那么在创建对象的时候可以不调用init方法。

    有了alloc底层骚操作的经验后,打个断点调试下:

    NSObject *obj = [NSObject alloc];
    [obj init];
    

    这里allocinit分开写是为了避免被优化。这时候调用流程和源码看到的相同。

    那么修改下调用逻辑:

    NSObject *obj = [[NSObject alloc] init];
    

    alloc init一起调用后会先进入objc_alloc_init方法。

    objc_alloc_init

    id
    objc_alloc_init(Class cls)
    {
        return [callAlloc(cls, true/*checkNil*/, false/*allocWithZone*/) init];
    }
    

    objc_alloc_init调用了callAllocinit

    ⚠️不同版本的系统下调用逻辑会有不同。

    七、new源码探索

    既然alloc initnew都能创建对象,那么它们之间有什么区别呢?
    new

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

    alloc init一起调用的不同点是checkNil传递的是fasle
    源码调试发现new调用的是objc_opt_new

    // Calls [cls new]
    id
    objc_opt_new(Class cls)
    {
    #if __OBJC2__
        if (fastpath(cls && !cls->ISA()->hasCustomCore())) {
            return [callAlloc(cls, false/*checkNil*/) init];
        }
    #endif
        return ((id(*)(id, SEL))objc_msgSend)(cls, @selector(new));
    }
    

    objc2下也是callAllocinit

    • init方法内部默认没有进行任何操作,只是返回了对象本身。
    • allot initnew底层实现一致,都是调用callAllocinit。所以如果自定义了init方法调用两者效果相同。

    objc_alloc_initobjc_opt_new的绑定与objc_alloc的实现相同。同样的实现绑定的还有:

    const char *AppleObjCTrampolineHandler::g_opt_dispatch_names[] = {
       "objc_alloc",//alloc
       "objc_autorelease",//autorelease
       "objc_release",//release
       "objc_retain",//retain
       "objc_alloc_init",// alloc init
       "objc_allocWithZone",//allocWithZone
       "objc_opt_class",//class
       "objc_opt_isKindOfClass",//isKindOfClass
       "objc_opt_new",//new
       "objc_opt_respondsToSelector",//respondsToSelector
       "objc_opt_self",//self
    };
    

    总结

    alloc调用过程:

    • objc_alloc
      • alloc底层首先调用的是objc_alloc
      • objc_allocalloc是在llvm编译阶段进行关联的。苹果会对系统特殊函数做hook进行标记。
    • callAlloc判断应该初始化的分支。
    • _class_createInstanceFromZone进行真正的开辟和关联操作:
      • instacneSize计算应该开辟的内存空间。
        • alignedInstanceSize内部进行字节对齐。
        • fastInstanceSize内部会进行内存对齐。
      • calloc开辟内存空间。
      • initInstanceIsa关联isa与创建的对象。
    • init & new
      • init方法内部默认没有进行任何操作,只是为了方便扩展。
      • allot initnew底层实现一致,都是调用callAllocinit

    相关文章

      网友评论

          本文标题:OC alloc 底层探索

          本文链接:https://www.haomeiwen.com/subject/ercfeltx.html