美文网首页
Objc4-818底层探索(一):alloc

Objc4-818底层探索(一):alloc

作者: ShawnAlex | 来源:发表于2021-06-07 16:10 被阅读0次

    首先先看个例子:

        TestObj *obj1 = [TestObj alloc];
        TestObj *obj2 = [obj1 init];
        TestObj *obj3 = [obj1 init];
           
        NSLog(@"%@ - %p - %p", obj1, obj1, &obj1);
        NSLog(@"%@ - %p - %p", obj2, obj2, &obj2);
        NSLog(@"%@ - %p - %p", obj3, obj3, &obj3);
    
    

    他们的打印情况为

    问题8-答案

    首先了解下, 三个打印依次是打印 对象内容, 对象指针指向的内存地址, 指针地址
    %@: 打印的是对象的内容
    %p → &p1: 打印的是指向对象内存的指针地址
    %p → p1: 打印的是指针地址

    那么

    • 第一列: obj1, ob2, obj3 相等, 打印的是对象内容都是TestObj开辟的内存空间

    • 第二列: obj1, obj2, obj3 相等, 留意下init是不会对指针进行操作的, 而%p打印的是对象指针, obj1、obj2、 obj3, 三个都是指向同一片内存区域( [TestObj alloc]开辟的), 所以不变

    • 第三列: &obj1, &ob2, &obj3 不相等, 打印的是指针地址, obj1、obj2、 obj3三个不同指针, 地址不同

    指向图

    (例子详细内容可看 IOS面试题 --- 类相关中的问题8)

    其实这里面就引出一个问题? alloc 究竟做了什么?

    alloc探索

    通常我们对alloc了解是, 它做了初始化, 开辟了一块内存空间, 那它底层究竟怎么做的呢? 首先我们需要一份objc-818源码:
    编译好的objc4-818源码下载: https://github.com/Lv100-ShawnAlex/objc4-818.git
    源码探索常用方法: https://www.jianshu.com/p/63988f940c90

    源码有了, 先建立一个SATest类继承NSObject, 并做alloc初始化

    建立一个SATest类

    cmd + 点击查看alloc源码

    SATest点击查看源码

    依次点击进入

    + (id)alloc {
        
        return _objc_rootAlloc(self);
    
    }
    
    
    id
    _objc_rootAlloc(Class cls)
    {
        return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
    }
    
    static ALWAYS_INLINE id
    callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
    {
    // OBJC有1.0版本2.0版本, 现在我们大部门都用2.0版本
    #if __OBJC2__
        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);
        }
        // 发送 alloc 消息
        return ((id(*)(id, SEL))objc_msgSend)(cls, @selector(alloc));
    }
    

    走到这里是时候, 其实就会很迷茫, 因为我们不知道究竟走了哪个判断?那么可以加个断点运行一下

    源码加断点运行

    这块留意下因为系统很多对象alloc方法, 都会走这里, 所以要确保是我们建的类SATest进入

    错误的例子
    错误的例子
    正确的例子

    最好main中的对象先加断点, 等main中断点确定走到之后再加/重新打开对应方法的断点


    main先加断点 正确的例子

    po: lldb命令读出/输出值, 打印对象




    当我们在这边打断点跟流程的时候, 可看到, 流程先走了return ((id(*)(id, SEL))objc_msgSend)(cls, @selector(alloc));, 然后又来一遍走了
    if (fastpath(!cls->ISA()->hasCustomAWZ()))这个判断里面的return _objc_rootAllocWithZone(cls, nil);

    为什么会走2次呢? 我们这里可以打开汇编(Debug→Debug Workflow → Always Show Disassembly)看一下

    打开汇编 走了objc_alloc

    可看到, 他并没有走alloc, 而走了objc_alloc, 这个objc_alloc是什么呢? 我们先看下源码, 在NSObjec.mm中可以找到调用了callAlloc方法

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

    那么我们 cmd + 点击进入的方法走没走呢?

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

    我们依次(id)objc_alloc(Class cls), + (id)alloc, callAlloc(Class cls, bool checkNil, bool allocWithZone=false)分别加个断点, 判断下是这里代码走入情况

    objc_alloc断点 alloc断点 callAlloc断点

    可以看到依次走入顺序

    objc_alloccallAllocobjc_msgSendalloc_objc_rootAlloccallAlloc_objc_rootAllocWithZone

    alloc流程图

    接下来介绍下slowpathfastpath(后面也会用到)

    #define fastpath(x) (__builtin_expect(bool(x), 1))
    #define slowpath(x) (__builtin_expect(bool(x), 0))
    

    其中__builtin_expect指令是由编译器gcc引入的。 __builtin_expect(EXP, N), 表示 EXP==N的概率很大。
    目的 : 编译器可以对代码进行优化, 以减少指令跳转带来的性能下降。(性能优化)
    作用 : 允许程序员将最有可能执行的分支告诉编译器
    写法 : __builtin_expect(EXP, N)。表示 EXP==N 的概率很大。

    • fastpath: __builtin_expect(bool(x), 1)。表示x值为真的概率很大如果方法放在if判断中, 执行真的if的几率很大。

    • slowpath: __builtin_expect(bool(x), 0)。表示x值为假的概率很大如果方法放在if判断中, 执行假的else的几率很大

    在日常的开发中, 也可以通过设置来优化编译器 , 达到性能优化的目的, 设置位置 Build SettingsOptimization Level

    Optimization Level



    看下这里走2次原因, 涉及知识点比较多, 此处仅仅是探索猜测, 后续补完 (这块我也是在探索, 可直接跳到下面alloc 三大核心)

    fastpath(!cls->ISA()->hasCustomAWZ()
    
        bool hasCustomAWZ() const {
            return !cache.getBit(FAST_CACHE_HAS_DEFAULT_AWZ);
        }
    
    // class or superclass has default alloc/allocWithZone: implementation
    // Note this is is stored in the metaclass.
    // 类或父类有默认 alloc/allocWithZone 实现
    // 注意,它存储在元类中。
    #define FAST_CACHE_HAS_DEFAULT_AWZ    (1<<14)
    

    SATest 继承NSObject, NSObject有个默认的isa, 但此时isa并没有做任何初始化(objc_object::initIsa), 也可以理解成空白isa

    • 系统默认走了(这块需要看下llmv)objc_alloccallAlloc

    • 第一次进入:

      • cls有值if (slowpath(checkNil && !cls)) return nil;这个判断不会走

      • fastpath(!cls->ISA()->hasCustomAWZ(), 因为这个值FAST_CACHE_HAS_DEFAULT_AWZ存储在元类中, 此时父类链, 元类都没关联确认, 所以 cache.getBit(FAST_CACHE_HAS_DEFAULT_AWZ)false, !一下为true, 外层又!了一下还是为false, 所以首次不走这里。

      • allocWithZone传入的也是false, 所以走消息转发((id(*)(id, SEL))objc_msgSend)(cls, @selector(alloc))

    • 消息转发:
      • 快速查找: cache中并没有传入类的alloc方法, 所以直接进慢速查找流程
      • 慢速查找: 快速查找没有找到走 lookUpImpOrForward 慢速查找流程
        • 跟流程走这里 cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE);

    这块跟一下 realizeAndInitializeIfNeeded_locked源码

    /***********************************************************************
    * realizeAndInitializeIfNeeded_locked
    * Realize the given class if not already realized, and initialize it if
    * not already initialized.
    * inst is an instance of cls or a subclass, or nil if none is known.
    * cls is the class to initialize and realize.
    * initializer is true to initialize the class, false to skip initialization.
    **********************************************************************/
    static Class
    realizeAndInitializeIfNeeded_locked(id inst, Class cls, bool initialize)
    {
        runtimeLock.assertLocked();
        // 判断当前类是否实现
        if (slowpath(!cls->isRealized())) {
            cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
            // runtimeLock may have been dropped but is now locked again
        }
       
        // 判断当前类是否初始化
        if (slowpath(initialize && !cls->isInitialized())) {
            cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
            // runtimeLock may have been dropped but is now locked again
    
            // If sel == initialize, class_initialize will send +initialize and
            // then the messenger will send +initialize again after this
            // procedure finishes. Of course, if this is not being called
            // from the messenger then it won't happen. 2778172
        }
        return cls;
    }
    
    // Locking: caller must hold runtimeLock; this may drop and re-acquire it
    static Class initializeAndLeaveLocked(Class cls, id obj, mutex_t& lock)
    {
        return initializeAndMaybeRelock(cls, obj, lock, true);
    }
    
    /***********************************************************************
    * class_initialize.  Send the '+initialize' message on demand to any
    * uninitialized class. Force initialization of superclasses first.
    * inst is an instance of cls, or nil. Non-nil is better for performance.
    * Returns the class pointer. If the class was unrealized then 
    * it may be reallocated.
    * Locking: 
    *   runtimeLock must be held by the caller
    *   This function may drop the lock.
    *   On exit the lock is re-acquired or dropped as requested by leaveLocked.
    **********************************************************************/
    static Class initializeAndMaybeRelock(Class cls, id inst,
                                          mutex_t& lock, bool leaveLocked)
    {
        lock.assertLocked();
        ASSERT(cls->isRealized());
    
        if (cls->isInitialized()) {
            if (!leaveLocked) lock.unlock();
            return cls;
        }
    
        // Find the non-meta class for cls, if it is not already one.
        // The +initialize message is sent to the non-meta class object.
        Class nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst);
    
        // Realize the non-meta class if necessary.
        if (nonmeta->isRealized()) {
            // nonmeta is cls, which was already realized
            // OR nonmeta is distinct, but is already realized
            // - nothing else to do
            lock.unlock();
        } else {
            nonmeta = realizeClassMaybeSwiftAndUnlock(nonmeta, lock);
            // runtimeLock is now unlocked
            // fixme Swift can't relocate the class today,
            // but someday it will:
            cls = object_getClass(nonmeta);
        }
    
        // runtimeLock is now unlocked, for +initialize dispatch
        ASSERT(nonmeta->isRealized());
        initializeNonMetaClass(nonmeta);
    
        if (leaveLocked) runtimeLock.lock();
        return cls;
    }
    

    继续跟源码我们看到走了这里


    realizeClassMaybeSwiftAndUnlock
    static Class
    realizeClassMaybeSwiftAndUnlock(Class cls, mutex_t& lock)
    {
        return realizeClassMaybeSwiftMaybeRelock(cls, lock, false);
    }
    
    /***********************************************************************
    * realizeClassMaybeSwift (MaybeRelock / AndUnlock / AndLeaveLocked)
    * Realize a class that might be a Swift class.
    * Returns the real class structure for the class. 
    * Locking: 
    *   runtimeLock must be held on entry
    *   runtimeLock may be dropped during execution
    *   ...AndUnlock function leaves runtimeLock unlocked on exit
    *   ...AndLeaveLocked re-acquires runtimeLock if it was dropped
    * This complication avoids repeated lock transitions in some cases.
    **********************************************************************/
    static Class
    realizeClassMaybeSwiftMaybeRelock(Class cls, mutex_t& lock, bool leaveLocked)
    {
        lock.assertLocked();
    
        if (!cls->isSwiftStable_ButAllowLegacyForNow()) {
            // Non-Swift class. Realize it now with the lock still held.
            // fixme wrong in the future for objc subclasses of swift classes
            realizeClassWithoutSwift(cls, nil);
            if (!leaveLocked) lock.unlock();
        } else {
            // Swift class. We need to drop locks and call the Swift
            // runtime to initialize it.
            lock.unlock();
            cls = realizeSwiftClass(cls);
            ASSERT(cls->isRealized());    // callback must have provoked realization
            if (leaveLocked) lock.lock();
        }
    
        return cls;
    }
    

    这里走了 if (!cls->isSwiftStable_ButAllowLegacyForNow())判断进入realizeClassWithoutSwift(cls, nil);

    这里比较长, 我就截取部分图片, 可看到处理了一些rw, ro信息


    处理rw, ro信息

    往下可看到进入了这里针对于父类链, 元类做一些判断处理

    处理父类链/元类

    继续往下, 可看到进入这里initClassIsa

    image.png
    inline void 
    objc_object::initClassIsa(Class cls)
    {
        if (DisableNonpointerIsa  ||  cls->instancesRequireRawIsa()) {
            initIsa(cls, false/*not nonpointer*/, false);
        } else {
            initIsa(cls, true/*nonpointer*/, false);
        }
    }
    
    initisa

    之后进入isa的核心代码initisa, 这里主要是对isa做一些操作处理

    initisa

    可看到这里的确对isa做了一些字段赋值

    打印isa信息

    之后进入的object_getClass

    Class object_getClass(id obj)
    {
        if (obj) return obj->getIsa();
        else return Nil;
    }
    
    inline Class
    objc_object::getIsa() 
    {
        if (fastpath(!isTaggedPointer())) return ISA();
    
        extern objc_class OBJC_CLASS_$___NSUnrecognizedTaggedPointer;
        uintptr_t slot, ptr = (uintptr_t)this;
        Class cls;
    
        slot = (ptr >> _OBJC_TAG_SLOT_SHIFT) & _OBJC_TAG_SLOT_MASK;
        cls = objc_tag_classes[slot];
        if (slowpath(cls == (Class)&OBJC_CLASS_$___NSUnrecognizedTaggedPointer)) {
            slot = (ptr >> _OBJC_TAG_EXT_SLOT_SHIFT) & _OBJC_TAG_EXT_SLOT_MASK;
            cls = objc_tag_ext_classes[slot];
        }
        return cls;
    }
    

    因为isTaggedPointer此时为0, 所以!为真, 直接走return ISA(), 后续把一些剩余代码走完, 然后走第二次进入cAlloc

    return ISA()
    • 第二次进入:

    第二次的calloc来自于alloc而不是objc_alloc, 由于消息发送的是alloc方法

    • cls有值if (slowpath(checkNil && !cls)) return nil;这个判断不会走

    • fastpath(!cls->ISA()->hasCustomAWZ(), 元类, 父类链确认完成, ISA中信息已更改所以FAST_CACHE_HAS_DEFAULT_AWZ cache.getBit(FAST_CACHE_HAS_DEFAULT_AWZ)true, !一下为false, 外层又!了一下还是为true, 第二次走了这里

    两次打印isa bits也会发现bits不一样


    两次打印isa

    (此处仅仅是探索猜测, 后续补完)


    alloc 三大核心

    我们接着跟下源码

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

    alloc核心代码_class_createInstanceFromZone

    /***********************************************************************
    * class_createInstance
    * fixme
    * Locking: none
    *
    * Note: this function has been carefully written so that the fastpath
    * takes no branch.
    **********************************************************************/
    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
        // hasCxxCtor: C++/OC的析构器(类似于dealloc)占1位
        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);
            
        } 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);
    }
    

    alloc核心代码: instanceSize:计算开辟内存大小

    我们也是_class_createInstanceFromZone里打断点, 看一下源码流程

    _class_createInstanceFromZone
    1. 第一步肯定是留意一下是否是我们定义的类进入

    2.开始定义一些是否包含析构的 bool
    hasCxxCtor: C++/OC的析构器(类似于dealloc)占1位

    • 第一个bool hasCxxCtorfalse: cxxConstruct 为传入进来的为true, cls->hasCxxCtor()是否有析构器false, 然后 & 一下为false

    • 第二个bool hasCxxDtorfalse:cls->hasCxxCtor()同上为false

    • 第三个bool fast:falsecls->canAllocNonpointer()判断当前类是否已经关联isa, 为此并没有 false

    3.定义size主要用于储存后面的开辟空间大小值

    instanceSize
    1. 核心代码之一: size = cls->instanceSize(extraBytes);, 计算开辟内存空间大小并赋给size(extraBytes: 是否需要额外空间大小, 为0)

    首先我们了解下内存大小是由属性/成员变量决定, 这里可由data()->ro()->instanceSize源码知道

    // May be unaligned depending on class's ivars.

        uint32_t unalignedInstanceSize() const {
            ASSERT(isRealized());
            // 其中 instanceSize 是实例变量大小, 并且是编译完的干净内存大小
            return data()->ro()->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.
            // CF要求所有对象至少为16字节。不足16补齐16
            if (size < 16) size = 16;
            return size;
        }
    

    依次跟断点进入走了align16方法

    传入参数 size = 16, extra == 0, #define FAST_CACHE_ALLOC_DELTA16 0x0008 = 8,

    align16

    其中__builtin_constant_pgcc的内建函数 用于判断一个值是否为编译时常数,如果参数的值是常数,函数返回 1,否则返回 0, 这里很显然传入的是个变量所以走了下面的判断。(如果会走这里的话, 只能是 extra != 0)

    _flags
    #define FAST_CACHE_ALLOC_MASK16       0x1ff0 = 8176 = 0001 1111 1111 0000
    

    _flags系统会计算多次, 最后为32784 = 1000 0000 0001 0000
    两种做下 &运算

    0001 1111 1111 0000
    &
    1000 0000 0001 0000
    =
    0000 0000 0001 0000 = 16

    cache.fastInstanceSize(extraBytes)往下走, 这里align16是一个向上取整的16字节对齐方法

    static inline size_t align16(size_t x) {
        return (x + size_t(15)) & ~size_t(15);
    }
    

    ~: 非操作, 0变1, 1变0
    &: 与操作, 1 & 0 = 0; 1&1 = 1; 0&0 = 0

    这个方法核心在于转成二进制按位与

    例子1: x = 16 , 16二进制为 0001 0000, 15 二进制为0000 1111, ~15为1111 0000
    16 + 15 = 31 = 0001 1111
    那么有

    0001 1111
    &
    1111 0000
    =
    0001 0000 = 16

    例子2: x = 7 , 二进制为 0000 0111, 15 二进制为0000 1111, ~15为1111 0000
    7 + 15 = 22 = 0001 0110
    那么有

    0001 0110
    &
    1111 0000
    =
    0001 0000 = 16, 不足16倍数的向上取整取到16倍数为止, 当然size_t(7)为8字节对齐。

    所以上面传入16 + 0 - 8 = 8 计算完size = 16 返回, 继续往下走可看到走了第二个核心代码calloc

    calloc

    当然我们也看一下如果计算空间大小没走, 后面方法怎么走的

        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;
        }
    
    // Class's ivar size rounded up to a pointer-size boundary.
        uint32_t alignedInstanceSize() const {
            return word_align(unalignedInstanceSize());
        }
    
    #   define WORD_MASK 7UL
    
    static inline uint32_t word_align(uint32_t x) {
        return (x + WORD_MASK) & ~WORD_MASK;
    }
    

    这里可看出是个8字节对齐。这块我的理解是, obj对象实际占用是按8字节对齐,但之后系统还是会以16对齐分配大小,而快速创建一步到位, 直接按16字节对齐分配, 减少后面系统判断处理。

    calloc我们放在下一篇详细讲, 总结下instanceSize流程图

    instanceSize



    为什么需要8字节/16字节存储, 直接按本身字节数存不就好了吗? 例如内存条这样存放

    错误示范

    存完之后读取, 假如第一个是4字节, 第二个是8字节, 第三个是8字节, 第四个是4字节...
    当CPU开始读数据时候读完第一个4字节, 立马要变换读取长度8字节, 当读到第四个时候又要变化读取长度4字节读取, 这样每次都要做判断改变读取长度, 繁琐且会影响CPU速度。CPU意思: 大哥你别这样存了, 我难受!!!

    因为通常存储指针特别多, 即8字节比较多, 就规定存储时候都是8字节为一段进行存储, 不足也为8, 以空间换取时间

    接下来, 我们验证下是否真的是按我们想象的一样8字节对齐

    不同类型字节占用

    建立一个SATest类继承于NSObject, 并建立一些属性

    @interface SATest : NSObject
    
    @property (nonatomic, strong) NSString *name;
    @property (nonatomic, assign) int age;
    @property (nonatomic, assign) long height;
    @property (nonatomic, strong) NSString *hobby;
    
    #import <Foundation/Foundation.h>
    #import <objc/runtime.h>
    #import <malloc/malloc.h>
    #import "SATest.h"
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            
            SATest *test = [[SATest alloc] init];
            test.name = @"ShawnAlex";
            test.age = 18;
            test.height = 180;
            test.hobby = @"女";
            
            NSLog(@"%p", test);
        }
        return 0;
    }
    

    先介绍下我下面用到的一些lldb命令

    • x: 读取内存段
    • po: 读取一下内容
    • x/4gx: 以4个片段打印内存段
    • x/5gx: 以5个片段打印内存段
    读内存段

    可看到依次读出相应数据, 那么为什么没有hobby呢?

    因为4gx只会按4个片段读取, 第五个需要5gx(大于4都可)

    我们再增加一个bool b, 重新读取一下, 可看到还是5个片段

    新增bool

    那么bool去哪了呢?

    B100BFC2-F629-4CE0-8D2F-B61E7084BB9D.png

    其实这里, 系统帮我们优化了一下, 将intbool放在一起, 减少浪费


    总结:

    alloc流程图

    alloc开辟内存方法

    • 底层顺序 objc_alloccallAllocobjc_msgSendalloc_objc_rootAlloccallAlloc_objc_rootAllocWithZone_class_createInstanceFromZone

    • _class_createInstanceFromZonealloc的核心代码

      • instanceSize: 计算开辟空间大小
      • calloc: 开辟内存
      • initInstanceIsa: 对象与指针isa绑定

    相关文章

      网友评论

          本文标题:Objc4-818底层探索(一):alloc

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