最近公司没有那么忙了,闲来无事就看下的文章,看的累了,自己也想动手写的什么,写底层的东西是很单调的,想写的好,写的通俗易懂也不容易,但人总得去尝试一下。

1.alloc汇编检查

1.1.alloc直接检查

咱们直接点击alloc,进去检查一下源码

+ (instancetype)alloc OBJC_SWIFT_UNAVAILABLE("use object initializers instead");

到了这儿能知道的便是NSObject这个类,里面有alloc这个办法。然后就什么看不到了。相当于.h文件看到,.m的实现看不到。

那咱们只能改其它方式了。

1.2.alloc汇编检查

点击Debug -> Debug Workflow -> Always Show Disassembly。打开后断点就会进入汇编模式。

先 alloc一个对象。

Person *person = [Person alloc];

在 alloc那里打断点,运行代码

->  0x107bd5e83 <+51>:  movq   0x762e(%rip), %rax        ; (void *)0x0000000107bdd508: Person
    0x107bd5e8a <+58>:  movq   %rax, %rdi
    0x107bd5e8d <+61>:  callq  0x107bd63f6               ; symbol stub for: objc_alloc

咱们捉要点,能够看到objc_alloc,好,那咱们也把这个加入到断点里面。

iOS底层之alloc到底做了些什么(1)

再运行,咱们能够看到代码

libobjc.A.dylib`objc_alloc:
->  0x7fff20190b4d <+0>:  testq  %rdi, %rdi
    0x7fff20190b50 <+3>:  je     0x7fff20190b6c            ; <+31>
    0x7fff20190b52 <+5>:  movq   (%rdi), %rax
    0x7fff20190b55 <+8>:  testb  $0x40, 0x1d(%rax)
    0x7fff20190b59 <+12>: jne    0x7fff20188a75            ; _objc_rootAllocWithZone
    0x7fff20190b5f <+18>: movq   0x66bbf952(%rip), %rsi    ; "alloc"
    0x7fff20190b66 <+25>: jmpq   *0x5fe8d124(%rip)         ; (void *)0x00007fff20173780: objc_msgSend
    0x7fff20190b6c <+31>: xorl   %eax, %eax
    0x7fff20190b6e <+33>: retq  

咱们能看到的_objc_rootAllocWithZone。进去这个办法进行检查。

libobjc.A.dylib`_objc_rootAllocWithZone:
->  0x7fff20188a75 <+0>:   pushq  %rbp
    0x7fff20188a8b <+22>:  testw  %si, %si
    0x7fff20188a8e <+25>:  je     0x7fff20188aac            ; <+55>
    0x7fff20188a90 <+27>:  movl   $0x1, %edi
    0x7fff20188a95 <+32>:  callq  0x7fff2019323a            ; symbol stub for: calloc

咱们能够看到调用了calloc办法,咱们就知道是拓荒内存,进行分配内存空间。但更详细的咱们都无法通过汇编检查。

那怎样办呢,咱们接着看。

2.源码检查

源码来历能够通过苹果的openSource获取。

2.1.alloc

在NSObject.mm文件中,咱们终于能够看到alloc的实现了。

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

2.2._objec_rootAlloc

再点击_objec_rootAlloc办法,进去检查。

2.3.callAlloc

这时候进入了callocAlloc办法。

static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
    if (slowpath(checkNil && !cls)) return nil;
#if __OBJC2__
    if (fastpath(!cls->ISA()->hasCustomAWZ())) {
        if (fastpath(cls->canAllocFast())) {
            // No ctors, raw isa, etc. Go straight to the metal.
            bool dtor = cls->hasCxxDtor();
            id obj = (id)calloc(1, cls->bits.fastInstanceSize());
            if (slowpath(!obj)) return callBadAllocHandler(cls);
            obj->initInstanceIsa(cls, dtor);
            return obj;
        }
        else {
            // Has ctor or raw isa or something. Use the slower path.
            id obj = class_createInstance(cls, 0);
            if (slowpath(!obj)) return callBadAllocHandler(cls);
            return obj;
        }
    }
#endif
    if (allocWithZone) return [cls allocWithZone:nil];
    return [cls alloc];
}
这儿的要点是 id obj = class_createInstance(cls, 0);

2.3.class_createInstance

咱们看代码走到了class_createInstance办法,持续走

id
class_createInstance(Class cls, size_t extraBytes)
{
    return _class_createInstanceFromZone(cls, extraBytes, nil);
}

2.4._class_createInstanceFromZone

接着进来_class_createInstanceFromZone办法。

static __attribute__((always_inline))
id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone, 
                              bool cxxConstruct = true, 
                              size_t *outAllocatedSize = nil)
{    size_t size = cls->instanceSize(extraBytes);
    if (outAllocatedSize) *outAllocatedSize = size;
    id obj;
    if (!zone  &&  fast) {
        obj = (id)calloc(1, size);
        if (!obj) return nil;
        obj->initInstanceIsa(cls, hasCxxDtor);
    } 
    else {
        if (zone) {
            obj = (id)malloc_zone_calloc ((malloc_zone_t *)zone, 1, size);
        } else {
            obj = (id)calloc(1, size);
        }
        if (!obj) return nil; 
       obj->initIsa(cls);
    } 
   if (cxxConstruct && hasCxxCtor) {
        obj = _objc_constructOrFree(obj, cls);    
    }
    return obj;
}

到了这儿便是全文的要点了,这儿就return obj对象了。

那这儿的obj便是obj = (id)calloc(1, size);

很明显和汇编剖析相同,进行了calloc分配内存,那分配了多少呢,应该是和size有关。

那便是 size_t size = cls->instanceSize(extraBytes);

2.5.instanceSize

咱们进去看看instanceSize办法。

 size_t instanceSize(size_t extraBytes) {
        size_t size = alignedInstanceSize() + extraBytes;
        // CF requires all objects be at least 16 bytes.
        if (size < 16) size = 16;
        return size;
    }

初步结论是假如大小少于16,就回来16。

咱们再看看alignedInstanceSize办法。

    uint32_t alignedInstanceSize() {
        return word_align(unalignedInstanceSize());
    }

能够看到有内存对齐办法。

static inline uint32_t word_align(uint32_t x) {
    return (x + WORD_MASK) & ~WORD_MASK;
}
#   define WORD_MASK 7UL

这儿的WORD_MASK是等于7,咱们传进来的x是8,为什么是8呢,后边会提到。

那这儿是怎样算的呢,先上图

iOS底层之alloc到底做了些什么(1)

7为 0000 01111,~为取反,则为1111 1000,15为0000 1111。

1111 1000&0000 1111为0000 1000,0000 1000便是8。

能够得到的结论是内存对齐为8个字节

结合上面的if (size < 16) size = 16;所以size为16个字节。

那为什么要这么做呢,便是为了空间换取时刻,增加功率

2.6.initInstanceIsa

咱们持续接着看

obj->initInstanceIsa(cls, hasCxxDtor);

obj分配内存后,还不知道对应的是哪个类,所以调用initInstanceIsa办法。

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

这儿咱们再看initIsa办法。

inline void
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) 
{ 
    assert(!isTaggedPointer()); 
    if (!nonpointer) {
        isa.cls = cls;
    } else {
        assert(!DisableNonpointerIsa);
        assert(!cls->instancesRequireRawIsa());
        isa_t newisa(0);
#if SUPPORT_INDEXED_ISA
        assert(cls->classArrayIndex() > 0);
        newisa.bits = ISA_INDEX_MAGIC_VALUE;
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
        newisa.bits = ISA_MAGIC_VALUE;
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.shiftcls = (uintptr_t)cls >> 3;
#endif        
    isa = newisa;
    }
}

这儿的要点是调用了 isa.cls = cls;

意思便是把obj和咱们的Person类相关在一起,详细指向后边我会专门写一篇文章来阐明。

咱们都知道,咱们NSObject的类,就寄存这isa指针。

@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
    Class isa  OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}

isa指针占用8个字节,这就为什么上面内存对齐传的的x是8了,由于咱们Person是没有其它特点的,只有isa。

那到这儿,咱们就大约清楚这个流程了。

3.init和new

3.1.init

平常咱们总是alloc init,那咱们alloc就能回来对象了,那init有什么用呢

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

想到了吗,仍是回来自己,那这么做岂不是多此一举,你觉得呢

那必定不是啦,这是苹果为了咱们开发者更好的扩展内容用的,想想咱们平常初始化

- (instancetype)init
{
    self = [super init];
    if (self) {
        // 做的什么呢
    }
    return self;
}

这下子咱们就懂了吧。

3.2.new

平常咱们不用alloc init,会用new来替换,那new又做了些什么呢

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

好家伙,直接调用callAlloc,然后init办法,是不是就相当于alloc init了呢。

4.总结

上面看那么多的文字和代码也觉得累了,所以咱们用一张图来总结一下alloc流程。

iOS底层之alloc到底做了些什么(1)

那到这儿提出几个问题。

  1. 那这儿的isa又寄存着什么呢?
  2. calloc里面有做了什么事情?
  3. 内存对齐是怎样算的呢,还有是怎样寄存的呢。

这些问题,下一篇文章就会提到,敬请期待吧。