OC对象alloc流程探索

1,222 阅读3分钟

OC对象alloc流程探索

alloc方法

我们每天都在创建对象,今天就来看看alloc到底做了些什么。

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

我们在 xcode 工具栏 选择 Debug --> Debug Workflow --> Always Show Disassembly, 显示反汇编。 可以看到alloc其实直接调用了objc_alloc,让我们通过源码来看一下alloc的流程。

alloc流程

objc_alloc

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 (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 与fastpath

宏定义如下:

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

__builtin_expect

__builtin_expect() 是 GCC (version >= 2.96)提供给程序员使用的,目的是将“分支转移”的信息提供给编译器,这样编译器可以对代码进行优化,以减少指令跳转带来的性能下降。
这个指令的写法为: __builtin_expect(EXP, N)。
意思是:EXP==N的概率很大。

fastpath:
(__builtin_expect(bool(x), 1)),表示 x 的值为真的可能性更大;即执行 if 里面语句的机会更大。
slowpath:
(__builtin_expect(bool(x), 0)),表示 x 的值为假的可能性更大,即执行 else 里面语句的机会更大。

if (fastpath(!cls->ISA()->hasCustomAWZ())) {
        return _objc_rootAllocWithZone(cls, nil);
}
没有自定义allocWithZone,则会走到_objc_rootAllocWithZone

_objc_rootAllocWithZone

_class_createInstanceFromZone (核心代码)

核心代码:
1.cls->instanceSize:计算需要开辟的内存大小
2.calloc:申请内存,返回地址指针
3.obj->initInstanceIsa:将类与isa关联

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

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

计算每个属性的大小,这里有个重点就是字节对齐。

calloc:申请内存,返回地址指针

initInstanceIsa

inline void 
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) 
{ 
    ASSERT(!isTaggedPointer()); 
    
    if (!nonpointer) {
        isa = isa_t((uintptr_t)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;
        // 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
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.shiftcls = (uintptr_t)cls >> 3;
#endif

        // 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后,打印出的obj就是NSObject类型了。具体isa是如何关联的我们后面再分析。

init 和 new

init

+ (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的类方法和对象方法返回的都是id对象本身。
不同的是类方法返回了一个id类型的self,这是为了可以给开发者提供自定义构造方法的入口,通过id强转类型实现工厂设计,返回我们定义的类型。

new

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

实际上就是完成调用了alloc+init。

唯一区别: 采用[alloc init]方式可以灵活拓展,比如实现initWithXXX这种自定义的init方法,而采用new方式只能重写父类的init方法。

总结

alloc核心流程:
1.计算需要申请的内存空间大小。
2.为对象分配内存空间,并返回内存地址。
3.将类和开辟的内存空间关联起来。