底层

iOS-OC底层01:alloc init到底做了什么

2020-09-06  本文已影响0人  MonKey_Money

OC对象

简单案例来开启我们的探索之旅

     MyPerson *p1 = [MyPerson alloc];
    MyPerson *p2 = [p1 init];
    MyPerson *p3 = [p1 init];
    LGNSLog(@"%@ - %p - %p",p1,p1,&p1);
    LGNSLog(@"%@ - %p - %p",p2,p2,&p2);
    LGNSLog(@"%@ - %p - %p",p3,p3,&p3);
打印结果
<MyPerson: 0x600001d86230> - 0x600001d86230 - 0x7ffeea0e50b8
<MyPerson: 0x600001d86230> - 0x600001d86230 - 0x7ffeea0e50b0
<MyPerson: 0x600001d86230> - 0x600001d86230 - 0x7ffeea0e50a8

为什么p1,p2,p3指向同一个对象呢,又为什么指针地址不一样呢。


MyPerson.png

探索底层的方法

我们知道苹果有很多的库,如果我们想探索对象形成的过程,我们就要知道对象形成在哪里库里,下面有三种方法
1.下符号断点的形式直接跟流程
2.通过按住control+step into
3.汇编查看跟流程

1.下符号断点的形式直接跟流程

image.png

输入alloc
我们在MyPerson的初始化方法处打断点,另外我们在断点走到MyPerson初始化方法之前先Disable Breakpoint,当走到初始化方法时在Enable Breakpoint.因为在MyPerson初始化之前,项目中有其他的alloc的调用,如果UIViewController等等
如下图我们找到了alloc的库。


image.png

2.通过按住control+step into

在MyPerson初始化方法处打断点,然后control+step into,我们可见


image.png

然后objc_alloc下符号断点,可见开源库


image.png

3.汇编查看跟流程

通过下图开启汇编


image.png

在MyPerson初始化方法处打断点,可见


image.png
然后取objc_alloc下符号断点,下面操作和 通过按住control+step into相同.

定位objc源码

苹果开源源码汇总: https://opensource.apple.com
这个地址用的更直接 https://opensource.apple.com/tarballs/
编译源码,可参考https://github.com/LGCooci/objc4_debug

image.png

alloc init到底做了什么

123.png

alloc 关键方法分析

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

static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
//此时allocWithZone为true, checkNil为false 
{
#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);
    }
    return ((id(*)(id, SEL))objc_msgSend)(cls, @selector(alloc));
}

slowpath(x):(__builtin_expect(bool(x), 0))
fastpath(x):(__builtin_expect(bool(x), 1))

__builtin_expect 这个指令是gcc引入的,作用是允许程序员将最有可能执行的分支告诉编译器。这个指令的写法为:__builtin_expect(EXP, N)。
意思是:EXP==N的概率很大。
cls->ISA()->hasCustomAWZ()

 bool hasCustomAWZ() const {
        return !cache.getBit(FAST_CACHE_HAS_DEFAULT_AWZ);
    }
  bool getBit(uint16_t flags) const {
        return _flags & flags;
    }
// class or superclass has default alloc/allocWithZone: implementation
// Note this is is stored in the metaclass.
#define FAST_CACHE_HAS_DEFAULT_AWZ    (1<<14)

类或超类具有默认的alloc / allocWithZone:实现 注意,这存储在元类中。
因为没有实现所以!cls->ISA()->hasCustomAWZ())判断为true

计算申请内存大小

extraBytes为0
    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;
    }

// __builtin_constant_p.
// Gcc的内建函数 __builtin_constant_p 用于判断一个值是否为编译时常数,如果参数EXP 的值是常数,函数返回 1,否则返回 0
    bool hasFastInstanceSize(size_t extra) const
    {
//因为extra不是常数,所以 _flags & FAST_CACHE_ALLOC_MASK
        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);
        }
    }
16禁止对齐。如果是17,(17 +15)%16*16 = 32,如果是16 
(16 +15)%16*16 = 16
static inline size_t align16(size_t x) {
    return (x + size_t(15)) & ~size_t(15);
}

开辟内存空间

     obj = (id)calloc(1, size);

我们在这里打印obj ,po obj 结果是内存地址0x000000010070cd40

关联类

     obj->initInstanceIsa(cls, hasCxxDtor);

我们在这里打印obj ,po obj 结果 <LGPerson: 0x10070cd40>

探索NSObject 初始化方法

[NSObject alloc] 没有调用alloc 方法是调用
objc_alloc(Class cls) ->callAlloc(cls, true, false);

[[NSObject alloc] init]调用也是没有调用alloc方法
objc_alloc_init->[callAlloc(cls, true, false) init]

new 源码探索

一般在开发中,初始化除了init,还可以使用new,两者本质上并没有什么区别,以下是objc中new的源码实现,通过源码可以得知,new函数中直接调用了callAlloc函数(即alloc中分析的函数),且调用了init函数,所以可以得出new 其实就等价于 [alloc init]的结论

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

一般开发中并不建议使用new,主要是因为有时会重写init方法做一些自定义的操作,用new初始化可能会无法走到自定义的部分

上一篇 下一篇

猜你喜欢

热点阅读