alloc&&init 探究

587 阅读5分钟

NSObject对象简介:

Class、objc_object、id的定义,都基于objc_class,一个NSObject对象的就是objc_object类型的结构体指针objc_object *

typedef struct objc_class *Class; //objc_class的结构体指针objc_class *,重定义一个名字Class

/// Represents an instance of a class.
struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};

/// A pointer to an instance of a class.
typedef struct objc_object *id; //重定义一个名字id

alloc

在ios中创建对象中创建一个NSObject对象基本上都会用到alloc语法,想研究它需要访问源码(以objc-750为例)

首先创建一个NSObject对象

LSPerson *person = [[LSPerson alloc] init]

当我们使用源码进行调试是,可以点击alloc进入NSObject.mm文件中,这个文件包含着创建一个NSObject对象的逻辑

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

查看调用的实际是一个c方法,接着进入_objc_rootAlloc,发现调用callAlloc

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

进入callAlloc发现实现逻辑如下,这里的ALWAYS_INLINE实际上是封装的一个inline宏,为LVVM的优化之一,可以优化c函数的调用(实际会看不到此函数的调用)

此方法里面检查了类的hasCustomAWZ方法,即检测是否重写allocWithZone方法,如果可以快速创建(已经申请了响应对象空间等),则直接initInstanceIsa,否则走class_createInstance,然后直接创建对象成功,返回对象,这里探究class_createInstance做了些什么 另外,参数中的allocWithZone默认为false,当重写了allocWithZone方法后才会调用最后的allocWithZone方法

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())) {
        //没有自定义的allocWithZone走这里
        // No alloc/allocWithZone implementation. Go straight to the allocator.
        // fixme store hasCustomAWZ in the non-meta class and 
        // add it to canAllocFast's summary
        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

    // No shortcuts available.
    if (allocWithZone) return [cls allocWithZone:nil];
    return [cls alloc];
}

进入class_createInstance方法,发现调用了一个LVVM优化的函数_class_createInstanceFromZone,里面除了一些标识,主要方法有三个instanceSize、calloc、initInstanceIsa

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

static __attribute__((always_inline)) 
id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone, 
                              bool cxxConstruct = true, 
                              size_t *outAllocatedSize = nil)
{
    if (!cls) return nil;

    assert(cls->isRealized());

    // Read class's info bits all at once for performance
    bool hasCxxCtor = cls->hasCxxCtor();
    bool hasCxxDtor = cls->hasCxxDtor();
    bool fast = cls->canAllocNonpointer();

    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;

        // Use raw pointer isa on the assumption that they might be 
        // doing something weird with the zone or RR.
        obj->initIsa(cls);
    }

    if (cxxConstruct && hasCxxCtor) {
        obj = _objc_constructOrFree(obj, cls);
    }

    return obj;
}

instanceSize

此方法是计算对象预申请的空间大小,其中一共主要调用了alignedInstanceSize、word_align、unalignedInstanceSize个方法 先通过调用了alignedInstanceSize简介调用unalignedInstanceSize和、word_align两个方法来获取大小,

unalignedInstanceSize获取了是获取了对象ro中计算好的对象大小(后面会介绍系统怎么计算的)

word_align是字节对齐,模拟“进一法”将预申请字节总大小定位到8的整数倍,可以自行测试思考一下,例如:(12 + 7 )& ~7 = 16

最后,通过instanceSize方法,通过前面计算的总字节数,如果申请的内存大小小于16,那么定位到最低16位

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

uint32_t unalignedInstanceSize() {
    assert(isRealized());
    return data()->ro->instanceSize;
}

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

#   define WORD_MASK 7UL //64位对齐到8字节的整数倍
static inline uint32_t word_align(uint32_t x) {
    return (x + WORD_MASK) & ~WORD_MASK;
}

calloc

calloc为系统实际申请内存空间方法,实际上是对齐16字节的整数倍,通过malloc_size可以查看该对象申请空间大小

void *p = calloc(1, 24);
NSLog(@"%lu",malloc_size(p));

是系统代码实现如下,可以查看为对齐到16的整数倍,对齐实现方式和word_align有所不同,但殊途同归,这个和cpu硬件的单个内存长度有关

#define SHIFT_NANO_QUANTUM		4
#define NANO_REGIME_QUANTA_SIZE	(1 << SHIFT_NANO_QUANTUM)	// 16

static MALLOC_INLINE size_t
segregated_size_to_fit(nanozone_t *nanozone, size_t size, size_t *pKey)
{
	size_t k, slot_bytes;

	if (0 == size) {
		size = NANO_REGIME_QUANTA_SIZE; // Historical behavior
	}
	k = (size + NANO_REGIME_QUANTA_SIZE - 1) >> SHIFT_NANO_QUANTUM; // round up and shift for number of quanta
	slot_bytes = k << SHIFT_NANO_QUANTUM;							// multiply by power of two quanta size
	*pKey = k - 1;													// Zero-based!

	return slot_bytes;
}

initInstanceIsa

这一步直接更新isa,最终返回给最外层

inline void 
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
    assert(!cls->instancesRequireRawIsa());
    assert(hasCxxDtor == cls->hasCxxDtor());

    initIsa(cls, true, hasCxxDtor);
}

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);
        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;
        isa = newisa;
    }
}

ro->instanceSize的值怎么来的

前面了解到一个对象的创建过程,最终得到一个objc_object类型的对象,而objc_object对象里面存放的是一个objc_object(Class)类型的isa,可以测试isa实际大小为8字节

下面介绍一下系统分配规则

例如一个对象LSPerson,里面有三个属性,理论上创建对象大小应该为 isa + int + name + int = 8 + 4 + 8 + 4 = 24,字节对齐后,instanceSize为24,mallocSize大小为32

@interface LSPerson : NSObject

@property (nonatomic, assign) int height;
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) int age;

@end

扩展注意

现在系统为了提高访问速度,对参数进行了优化,现在一块内存地位长度8字节

以上面的LSPerson为例

以前的储存方式: 实际占用字节为16

现在的储存方式:实际占用字节为24

那么系统创建的大小是不是计算错了么:instanceSize = 8 + 8 + 8 + 8 = 32 ?

实际上现在用的属性位置系统会从小到大进行合理调整,即看到的属性可能为height、age、name,因此空间大小还是24不会有浪费现象

init

介绍完毕alloc,开始介绍他的搭档init,话不多少,上源码

会发现,无论是类方法还是平时调用的对象方法init,实际返回的都是self本身,因此,init方法实际上什么也没做?

这其实苹果给的一种设计模式,可以让开发者在此重写方法初始参数等

+ (id)init {
    return (id)self;
}

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

id
_objc_rootInit(id obj)
{
    return obj;
}

new

介绍一下平时会看到有些人调用new函数,知道了alloc之后,其逻辑就更好理解了,直接调用了callAlloc参数来创建,最后调用init,相当于少了一步_objc_rootAlloc的c函数调用,如果没有重写allocWithZone,那么实现一模一样

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

allocWithzone

最后看看一下allocWithzone,发现也是间接调用的class_createInstance

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

id
_objc_rootAllocWithZone(Class cls, malloc_zone_t *zone)
{
    id obj;
    (void)zone;
    obj = class_createInstance(cls, 0);
    if (slowpath(!obj)) obj = callBadAllocHandler(cls);
    return obj;
}