前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Objective-C内存管理原理探究(一)

Objective-C内存管理原理探究(一)

作者头像
MelonTeam
发布2018-01-04 15:38:57
9960
发布2018-01-04 15:38:57
举报
文章被收录于专栏:MelonTeam专栏MelonTeam专栏

导语 让我们通过源代码了解OC内存管理的机制。

前言

相信每个人在开发iOS的过程中都有过OC是如何管理内存的疑问,虽然大家都知道是基于引用计数的,但retain,release究竟做了什么,只是简单的将引用计数加减1吗?Autorelease又究竟做了什么?Weak又是怎样实现的?等等跟内存相关的问题~本系列文章就从源代码级别来探究下OC究竟是怎么管理内存的~计划分为三篇 1.基础引用计数方法探究 2.ARC内存管理探究 3.Autorelease实现探究 本文是第一篇~

本文使用的源代码是objc4-709

一、引用计数

说起OC的内存管理必须要先说下引用计数: 1.我们创建一个新对象时,该对像引用计数为1; 2.有一个新的指针关联到该对象时,他的引用计数就加1; 3.对象关联的某个指针不再指向他时,他的引用计数就减1; 4.对象的引用计数为0时,说明此对象不再被任何指针指向,这时我们就可以将对象销毁。 这便是引用计数。

二、alloc、init、retain,release实现

alloc实现

OC中声明一个NSObjec是总是会先调用下alloc,但这个alloc究竟是做了什么呢?

在NSObject.mm中可以找到alloc的实现

代码语言:javascript
复制
+ (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)
{
    if (slowpath(checkNil && !cls)) return nil;

#if __OBJC2__
    if (fastpath(!cls->ISA()->hasCustomAWZ())) {
        // 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];
}

大概流程如下: 1)slowpath(checkNil && !cls)

看下slowpathfastpath的定义

代码语言:javascript
复制
#define fastpath(x) (__builtin_expect(bool(x), 1))
#define slowpath(x) (__builtin_expect(bool(x), 0))

__builtin_expect起的是优化性能的作用 __builtin_expect((x),1) 表示 x 的值为真的可能性更大; __builtin_expect((x),0) 表示 x 的值为假的可能性更大。

从宏的定义中可以看出slowpath(checkNil && !cls)是对cls进行nil判断,但这里cls大概率不为nil所以这里使用slowpath

2)通过hasCustomAWZ,判断是否有自定义allocWithZone实现

代码语言:javascript
复制
    bool hasCustomAWZ() {
        return ! bits.hasDefaultAWZ();
    }



#if FAST_HAS_DEFAULT_AWZ
    bool hasDefaultAWZ() {
        return getBit(FAST_HAS_DEFAULT_AWZ);
    }
    void setHasDefaultAWZ() {
        setBits(FAST_HAS_DEFAULT_AWZ);
    }
    void setHasCustomAWZ() {
        clearBits(FAST_HAS_DEFAULT_AWZ);
    }
#else
    bool hasDefaultAWZ() {
        return data()->flags & RW_HAS_DEFAULT_AWZ;
    }
    void setHasDefaultAWZ() {
        data()->setFlags(RW_HAS_DEFAULT_AWZ);
    }
    void setHasCustomAWZ() {
        data()->clearFlags(RW_HAS_DEFAULT_AWZ);
    }
#endif

hasCustomAWZ用来判断是否有自定义的的allocWithZone方法,如果为YES则说明有自定义实现,则不能走默认逻辑。 hasCustomAWZ为YES时直接调用allocWithZone方法。

代码语言:javascript
复制
// Replaced by ObjectAlloc
+ (id)allocWithZone:(struct _NSZone *)zone {
    return _objc_rootAllocWithZone(self, (malloc_zone_t *)zone);
}

id
_objc_rootAllocWithZone(Class cls, malloc_zone_t *zone)
{
    id obj;

#if __OBJC2__
    // allocWithZone under __OBJC2__ ignores the zone parameter
    (void)zone;
    obj = class_createInstance(cls, 0);
#else
    if (!zone) {
        obj = class_createInstance(cls, 0);
    }
    else {
        obj = class_createInstanceFromZone(cls, 0, zone);
    }
#endif

    if (slowpath(!obj)) obj = callBadAllocHandler(cls);
    return obj;
}

可以看出allocWithZone实际上调用的也是class_createInstanceFromZone

3)hasCustomAWZ为NO时,还需要再次判断当前的class是否支持快速alloc。如果支持,直接调用calloc函数,申请bits.fastInstanceSize()大小的内存空间,如果创建失败,会调用callBadAllocHandler函数,如果不支持快速alloc则调用class_createInstance

我们再看看定义FAST_ALLOC的地方

代码语言:javascript
复制
#if !__LP64__

...

#elif 1
// Leaks-compatible version that steals low bits only.

// class or superclass has .cxx_construct implementation
#define RW_HAS_CXX_CTOR       (1<<18)
// class or superclass has .cxx_destruct implementation
#define RW_HAS_CXX_DTOR       (1<<17)
// class or superclass has default alloc/allocWithZone: implementation
// Note this is is stored in the metaclass.
#define RW_HAS_DEFAULT_AWZ    (1<<16)

// class is a Swift class
#define FAST_IS_SWIFT           (1UL<<0)
// class or superclass has default retain/release/autorelease/retainCount/
//   _tryRetain/_isDeallocating/retainWeakReference/allowsWeakReference
#define FAST_HAS_DEFAULT_RR     (1UL<<1)
// class's instances requires raw isa
#define FAST_REQUIRES_RAW_ISA   (1UL<<2)
// data pointer
#define FAST_DATA_MASK          0x00007ffffffffff8UL

#else
..
#define FAST_ALLOC              (1UL<<50)
// instance size in units of 16 bytes
//   or 0 if the instance size is too big in this field
//   This field must be LAST
#define FAST_SHIFTED_SIZE_SHIFT 51

这里有个#elif 1,所以现在其实FAST_ALLOC 是没有定义的,所以一定会调用class_createInstance创建对象。 在objc-runtime-new.mm中可以找到class_createInstance方法

代码语言:javascript
复制
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;
}

从源码可以看出其实真正调用的是_class_createInstanceFromZone

_class_createInstanceFromZone函数过程如下: 1、对 cls 判断 nil,如果 cls 是 nil 的话就直接返回 nil。

2、assert(cls->isRealized());判断该类是否已经做过realize,关于realize的详请可以参考这篇这篇,realize主要是一些数据的拷贝和整理对齐。

3、判断是否支持hasCxxCtorhasCxxDtor还有canAllocNonpointerhasCxxCtorhasCxxDtor是对 Objective-C++ 的支持,表示这个类是否有 C++ 类构造函数和析构函数,如果有的话,需要进行额外的工作。canAllocNonpointer我们不用太关心,这里OC 2.0以上基本上返回的都是true。

4、通过instanceSize获取该对像的大小

代码语言:javascript
复制
    // May be unaligned depending on class's ivars.
    uint32_t unalignedInstanceSize() {
        assert(isRealized());
        return data()->ro->instanceSize;
    }

    // Class's ivar size rounded up to a pointer-size boundary.
    uint32_t alignedInstanceSize() {
        return word_align(unalignedInstanceSize());
    }

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

通过代码我们可以看出来OC中的对象最小大小是16字节。

5、一般zone都是为false(NSZone 已经弃用),fast都是true,所以最后都是调用initInstanceIsa进行初始化

总结

最后我们用一张图概括下alloc的流程

init实现

代码语言:javascript
复制
- (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其实什么也没做,只是返回了对象本身。这里还有段注释说很多类可能没有调用[super inti],所以这个init其实作用并不是特别大。

SideTable介绍

在介绍retain的实现之前我们先介绍一个跟引用计数相关的数据结构SideTable

代码语言:javascript
复制
struct SideTable {
    spinlock_t slock;
    RefcountMap refcnts;
    weak_table_t weak_table;

    SideTable() {
        memset(&weak_table, 0, sizeof(weak_table));
    }

    ~SideTable() {
        _objc_fatal("Do not delete SideTable.");
    }

    void lock() { slock.lock(); }
    void unlock() { slock.unlock(); }
    void forceReset() { slock.forceReset(); }

    // Address-ordered lock discipline for a pair of side tables.

    template
    static void lockTwo(SideTable *lock1, SideTable *lock2);
    template
    static void unlockTwo(SideTable *lock1, SideTable *lock2);
};

其中RefcountMap refcnts存放的就是引用计数,slock是同步锁,weak_table是weak table跟ARC的weak实现相关。

引用计数器定义了几个重要的宏,用来存储一些跟引用计数相关的标志位。

代码语言:javascript
复制
// The order of these bits is important.
#ifdef __LP64__
#   define WORD_BITS 64
#else
#   define WORD_BITS 32
#endif

#define SIDE_TABLE_WEAKLY_REFERENCED (1UL<<0)
#define SIDE_TABLE_DEALLOCATING      (1UL<<1)  // MSB-ward of weak bit
#define SIDE_TABLE_RC_ONE            (1UL<<2)  // MSB-ward of deallocating bit
#define SIDE_TABLE_RC_PINNED         (1UL<WORD_BITS-1))

#define SIDE_TABLE_RC_SHIFT 2
#define SIDE_TABLE_FLAG_MASK (SIDE_TABLE_RC_ONE-1)

每个retainCount都是size_t,是无符号整形 所以根据上面的宏定义,一个retainCount的位区域划分如下图

SIDE_TABLE_WEAKLY_REFERENCED (内存的第 1位)标识该对象是否有过 weak 对象; SIDE_TABLE_DEALLOCATING(内存的第 2 位),标识该对象是否正在 dealloc。 SIDE_TABLE_RC_ONE (内存的第 3 位),存放引用计数数值(三位之后都用来存放引用计数数值)。 所以每次我们引用计数加一时,真正加的是4,在取出真正的引用计数时需要右移两位

retain实现

代码语言:javascript
复制
// Equivalent to calling [this retain], with shortcuts if there is no override
// Replaced by ObjectAlloc
- (id)retain {
    return ((id)self)->rootRetain();
}

inline id 
objc_object::rootRetain()
{
    if (isTaggedPointer()) return (id)this;
    return sidetable_retain();
}

retain 函数只是直接调用了rootRetainrootRetain首先判断是否为TaggedPointer,之后对 isa 中是否有自定义 retain 和 release,如果没有自定义的实现,则进入sidetable_retain 函数,否则的话直接向对象发送 retain 消息。 判断是否为TaggedPointer,是因为针对TaggedPointer会做一些优化。本文就不详细叙述~我们在下一讲中在进行介绍。

代码语言:javascript
复制
id
objc_object::sidetable_retain()
{
#if SUPPORT_NONPOINTER_ISA
    assert(!isa.nonpointer);
#endif
    SideTable& table = SideTables()[this];

    table.lock();
    size_t& refcntStorage = table.refcnts[this];
    if (! (refcntStorage & SIDE_TABLE_RC_PINNED)) {
        refcntStorage += SIDE_TABLE_RC_ONE;
    }
    table.unlock();

    return (id)this;
}

sidetable_retain先从SideTables中取出SideTable,然后从table.refcnts中取出自己的retainCount存储区域,refcntStorage += SIDE_TABLE_RC_ONE,而且这里有上锁,所以retain是线程安全的。之所以有SideTables,是为了减小锁的粒度,如果直接存放在一个SideTable中,那这个SideTable就是全局上锁了势必性能不好。

总结

  • SideTable包含着一个自旋锁slock来防止操作时可能出现的多线程读取问题、一个弱引用表weak_table以及引用计数表refcnts。
  • RefcountMap 通过Map的结构存储了对象持有者的地址以及引用计数
  • SideTables中存放SideTable,SideTable中存放refcnts,是两层嵌套。
  • 由于bitMask的使用,每次retain引用计数的值实际上增加了(1 « 2) = 4而不是1

release实现

代码语言:javascript
复制
- (oneway void)release {
    ((id)self)->rootRelease();
}

inline bool 
objc_object::rootRelease()
{
    if (isTaggedPointer()) return false;
    return sidetable_release(true);
}

// rdar://20206767
// return uintptr_t instead of bool so that the various raw-isa 
// -release paths all return zero in eax
uintptr_t
objc_object::sidetable_release(bool performDealloc)
{
#if SUPPORT_NONPOINTER_ISA
    assert(!isa.nonpointer);
#endif
    SideTable& table = SideTables()[this];

    bool do_dealloc = false;

    table.lock();
    RefcountMap::iterator it = table.refcnts.find(this);
    if (it == table.refcnts.end()) {
        do_dealloc = true;
        table.refcnts[this] = SIDE_TABLE_DEALLOCATING;
    } else if (it->second < SIDE_TABLE_DEALLOCATING) {
        // SIDE_TABLE_WEAKLY_REFERENCED may be set. Don't change it.
        do_dealloc = true;
        it->second |= SIDE_TABLE_DEALLOCATING;
    } else if (! (it->second & SIDE_TABLE_RC_PINNED)) {
        it->second -= SIDE_TABLE_RC_ONE;
    }
    table.unlock();
    if (do_dealloc  &&  performDealloc) {
        ((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc);
    }
    return do_dealloc;
}

release需要判断最终是否需要调用dealloc,所以会复杂些,流程如下: 1)先遍历 table.refcnts,寻找是否有对应对象的retainCount,如果不存在do_dealloc = true 2)如果存在再判断标志位是否小于SIDE_TABLE_DEALLOCATING(引用计数是否为0,可以发现这个时候我们还没有减1,但已经跟0进行判断了,所以可以发现表中存的引用计数实际上存的是真实的引用计数-1),如果小于do_dealloc = true 3)否则就减去一个SIDE_TABLE_RC_ONE(引用计数-1) 4)最后看do_dealloc是否需要调用dealloc。

三、retainCount、dealloc的实现

retainCount实现

代码语言:javascript
复制
- (NSUInteger)retainCount {
    return ((id)self)->rootRetainCount();
}

inline uintptr_t 
objc_object::rootRetainCount()
{
    if (isTaggedPointer()) return (uintptr_t)this;
    return sidetable_retainCount();
}

uintptr_t
objc_object::sidetable_retainCount()
{
    SideTable& table = SideTables()[this];

    size_t refcnt_result = 1;

    table.lock();
    RefcountMap::iterator it = table.refcnts.find(this);
    if (it != table.refcnts.end()) {
        // this is valid for SIDE_TABLE_RC_PINNED too
        refcnt_result += it->second >> SIDE_TABLE_RC_SHIFT;
    }
    table.unlock();
    return refcnt_result;
}

流程如下: 1)声明refcnt_result并且在初始化的时候设为1(用来+1) 2)从SideTables找出对应对象的SideTable。 3)判断refcnts中是否存该对象,如果存在,先将值» SIDE_TABLE_RC_SHIFT得到真实的引用计数值,然后返回引用计数+1

至于» SIDE_TABLE_RC_SHIFT和加1是因为我们上面有提到,数据结构的后两位用了做了别的用途,所以有» SIDE_TABLE_RC_SHIFT。而表中存的值实际上真实的引用计数值-1。

dealloc的实现

代码语言:javascript
复制
- (void)dealloc {
    _objc_rootDealloc(self);
}

void
_objc_rootDealloc(id obj)
{
    assert(obj);

    obj->rootDealloc();
}

inline void
objc_object::rootDealloc()
{
    if (isTaggedPointer()) return;
    object_dispose((id)this);
}

id 
object_dispose(id obj)
{
    if (!obj) return nil;

    objc_destructInstance(obj);    
    free(obj);

    return nil;
}

void *objc_destructInstance(id obj) 
{
    if (obj) {
        // Read all of the flags at once for performance.
        bool cxx = obj->hasCxxDtor();
        bool assoc = obj->hasAssociatedObjects();

        // This order is important.
        if (cxx) object_cxxDestruct(obj);
        if (assoc) _object_remove_assocations(obj);
        obj->clearDeallocating();
    }

    return obj;
}

可以发现,dealloc主要是调用objc_destructInstance方法,objc_destructInstance中做了很多事情,清理关联对象,ARC下释放成员变量等等,这里我们留到下一讲再详细叙述。

参考目录

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2017-07-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 一、引用计数
  • 二、alloc、init、retain,release实现
    • alloc实现
      • 总结
  • init实现
    • SideTable介绍
      • 总结
  • retain实现
  • release实现
  • 三、retainCount、dealloc的实现
    • retainCount实现
      • dealloc的实现
      • 参考目录
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档