iOS开发实用技术程序员

OC的内存管理方式

2019-03-08  本文已影响28人  taobingzhi

MRC

手动内存管理,每一次的retain,new,alloc都对应一次release,autorelease;

ARC

自动引用计数,由编译器在编译期间用更底层的C接口实现retain/release/autorelease,不需要手动释放。(底层的原理和接口api待会讲解)

ARC的规则

当对象创建时,引用计数为1;当一个强指针(strong或者retain)指向该对象时,引用计数+1;当强指针不在指向该对象时,引用计数-1;当对象的引用计数为0时,说明这个对象不被任何指针指向,可以销毁,回收内存。

ARC的内部实现

ARC背后的引用计数主要依赖于三个方法:

下面我们来看看runtime的源码。

- (id)retain {
    return ((id)self)->rootRetain();
}
inline id objc_object::rootRetain()
{
    if (isTaggedPointer()) return (id)this;
    return sidetable_retain();
}
id objc_object::sidetable_retain()
{
    //获取table
    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;
}

我们可以看到底层的C语言,retain方法实际是调用sidetable_retain();方法,在该方法中,有一个SideTable的结构体来存储引用计数,下面来看一下这个结构体的组成。

typedef objc::DenseMap<DisguisedPtr<objc_object>,size_t,true> RefcountMap;
struct SideTable {
       spinlock_t slock;
       RefcountMap refcnts;
       weak_table_t weak_table;
       //省略
}

可以看到,这个数据结构就是存储了一个自旋锁,一个引用计数map。这个引用计数的map以对象的地址为key,引用计数作为value。到这里,retain的底层实现就很清楚了。
再来看看release的实现:

SideTable& table = SideTables()[this];
    bool do_dealloc = false;
    table.lock();
    //找到对应地址的
    RefcountMap::iterator it = table.refcnts.find(this);
    if (it == table.refcnts.end()) { //找不到的话,执行dellloc
        do_dealloc = true;
        table.refcnts[this] = SIDE_TABLE_DEALLOCATING;
    } else if (it->second < SIDE_TABLE_DEALLOCATING) {//引用计数小于阈值,dealloc
        do_dealloc = true;
        it->second |= SIDE_TABLE_DEALLOCATING;
    } else if (! (it->second & SIDE_TABLE_RC_PINNED)) {
    //引用计数减去1
        it->second -= SIDE_TABLE_RC_ONE;
    }
    table.unlock();
    if (do_dealloc  &&  performDealloc) {
        //执行dealloc
        ((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc);
    }
    return do_dealloc;

很简单的逻辑:查找map,对引用计数减1,如果引用计数小于阈值或者找不到引用计数,则调用dealloc。

我们知道非autorelease对象在超出作用域的时候就会被release掉,而autorelease对象在什么情况下会被释放呢?

下面介绍一下autorelease pool。

Autorelease Pool

事实上在iOS 程序启动之后,主线程会启动一个Runloop,这个Runloop在每一次循环是被自动释放池包裹的,在合适的时候对池子进行清空。

也就是说AutoreleasePool创建是在一个RunLoop事件开始之前(push),AutoreleasePool释放是在一个RunLoop事件即将结束之前(pop)。AutoreleasePool里的Autorelease对象的加入是在RunLoop事件中,AutoreleasePool里的Autorelease对象的释放是在AutoreleasePool释放时。

autorelease pool的底层实现:
Autorelease Pool主要是三个方法实现:
objc_autoreleasePush();->objc_autorelease();->objc_autoreleasePop();
研究源码可以发现,一个线程的autorelease pool是一个指针栈,栈中存放的指针指向需要release的对象或者POOL_SENTINEL(哨兵对象,用于分隔autorelease pool,现在叫POOL_BOUNDARY)。
栈中指向POOL_SENTINEL(即POOL_BOUNDARY)的指针就是autorelease pool的一个标记。当autorelease pool进行出栈操作,每一个比这个哨兵对象晚进栈的对象,都会release。

下面我们来看一下源码:
在终端中使用clang -rewrite-objc 命令将OC的main函数重写成C++的实现:

int main(int argc, const char * argv[]) {

    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_kb_06b822gn59df4d1zt99361xw0000gn_T_main_d39a79_mi_0);
    }

    return 0;
}

可以看到一个__AtAutoreleasePool类型的局部变量__autoreleasepool,__AtAutoreleasePool的定义如下:

extern "C" __declspec(dllimport) void * objc_autoreleasePoolPush(void);
extern "C" __declspec(dllimport) void objc_autoreleasePoolPop(void *);

struct __AtAutoreleasePool {
  __AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
  ~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
  void * atautoreleasepoolobj;
};

这里一个是构造函数一个是析构函数(局部变量的构造函数是在程序执行到声明对象的位置时调用,而析构函数是在程序执行到离开这个对象的作用域时调用)。

因此,自动释放池的执行过程就是objc_autoreleasePoolPush() —> [object autorelease] —> objc_autoreleasePoolPop(void )。

来看一下push和pop两个方法的实现:

void *objc_autoreleasePoolPush(void) {
    return AutoreleasePoolPage::push();
}

void objc_autoreleasePoolPop(void *ctxt) {
    AutoreleasePoolPage::pop(ctxt);
}

下面介绍一下AutoreleasePoolPage的实现。

AutoreleasePoolPage实现

AutoreleasePoolPage介绍

是c++中的一个类,它在NSObject.mm中的定义如下:

class AutoreleasePoolPage {
#   define EMPTY_POOL_PLACEHOLDER ((id*)1)

#   define POOL_BOUNDARY nil
    static pthread_key_t const key = AUTORELEASE_POOL_KEY;
    static uint8_t const SCRIBBLE = 0xA3;  // 0xA3A3A3A3 after releasing
    static size_t const SIZE = 
#if PROTECT_AUTORELEASEPOOL
        PAGE_MAX_SIZE;  // must be multiple of vm page size
#else
        PAGE_MAX_SIZE;  // size and alignment, power of 2
#endif
    static size_t const COUNT = SIZE / sizeof(id);

    magic_t const magic;
    id *next;
    pthread_t const thread;
    AutoreleasePoolPage * const parent;
    AutoreleasePoolPage *child;
    uint32_t const depth;
    uint32_t hiwat;
};

介绍一下参数:

双向链表

自动释放池并没有单独的结构,而是由若干个AutoreleasePoolPage以双向链表的形式组合而成的栈结构(对应parent指针和child指针)
parent指向前一个page,child指向下一个page。
当一个page的空间被占满时,会新建一个page对象,连接链表,后来的autorelease对象在新的page中加入。

objc_autoreleasePoolPush

autoreleasePoolPush.jpg

调用objc_autoreleasePoolPush方法时会把边界对象放进栈顶,然后返回边界对象,用于objc_autoreleasePoolPop。

atautoreleasepoolobj = objc_autoreleasePoolPush();
///atautoreleasepoolobj就是返回的边界对象(POOL_BOUNDARY)

push实现如下:

void *objc_autoreleasePoolPush(void) {
    return AutoreleasePoolPage::push();
}

static inline void *push() {
   return autoreleaseFast(POOL_BOUNDARY);
}

在这里会进入一个比较关键的方法autoreleaseFast,并传入边界对象;

/*
有 hotPage 并且当前 page 不满,调用 page->add(obj) 方法将对象添加至 AutoreleasePoolPage 的栈中
有 hotPage 并且当前 page 已满,调用 autoreleaseFullPage 初始化一个新的页,调用 page->add(obj) 方法将对象添加至 AutoreleasePoolPage 的栈中
无 hotPage,调用 autoreleaseNoPage 创建一个 hotPage,调用 page->add(obj) 方法将对象添加至 AutoreleasePoolPage 的栈中
*/
static inline id *autoreleaseFast(id obj)
{
   AutoreleasePoolPage *page = hotPage();
   if (page && !page->full()) {
       return page->add(obj);
   } else if (page) {
       return autoreleaseFullPage(obj, page);
   } else {
       return autoreleaseNoPage(obj);
   }
}

最后都会调用page->add(obj)将对象添加到自动释放池中。hotPage可以理解为当前正在使用的AutoreleasePoolPage。

AutoreleasePoolPage::autorelease(id obj)

inline id objc_object::rootAutorelease() {
    if (isTaggedPointer()) return (id)this;
    if (prepareOptimizedReturn(ReturnAtPlus1)) return (id)this;

    return rootAutorelease2();
}

__attribute__((noinline,used)) id objc_object::rootAutorelease2() {
    return AutoreleasePoolPage::autorelease((id)this);
}

static inline id autorelease(id obj) {
   id *dest __unused = autoreleaseFast(obj);
   return obj;
}

autorelease方法和push方法一样,关键代码都是调用autoreleaseFast函数向自动释放池的链表栈中添加一个对象,不过push函数入栈的是一个边界对象,而autorelease函数入栈的是需要加入释放池的对象。

objc_autoreleasePoolPop

autoreleasePoolPop.jpg

自动释放池释放是传入push返回的边界对象,然后将边界对象指向的这一页AutoreleasePoolPage内的对象释放。

objc_autoreleasePoolPop(atautoreleasepoolobj);

AutoreleasePoolPage::pop()实现:

static inline void pop(void *token)   // token指针指向栈顶的地址
{
    AutoreleasePoolPage *page;
    id *stop;

    page = pageForPointer(token);   // 通过栈顶的地址找到对应的page
    stop = (id *)token;
    if (DebugPoolAllocation  &&  *stop != POOL_SENTINEL) {
        // This check is not valid with DebugPoolAllocation off
        // after an autorelease with a pool page but no pool in place.
        _objc_fatal("invalid or prematurely-freed autorelease pool %p; ", 
                    token);
    }

    if (PrintPoolHiwat) printHiwat();   // 记录最高水位标记

    page->releaseUntil(stop);   // 从栈顶开始操作出栈,并向栈中的对象发送release消息,直到遇到第一个哨兵对象

    // memory: delete empty children
    // 删除空掉的节点
    if (DebugPoolAllocation  &&  page->empty()) {
        // special case: delete everything during page-per-pool debugging
        AutoreleasePoolPage *parent = page->parent;
        page->kill();
        setHotPage(parent);
    } else if (DebugMissingPools  &&  page->empty()  &&  !page->parent) {
        // special case: delete everything for pop(top) 
        // when debugging missing autorelease pools
        page->kill();
        setHotPage(nil);
    } 
    else if (page->child) {
        // hysteresis: keep one empty child if page is more than half full
        if (page->lessThanHalfFull()) {
            page->child->kill();
        }
        else if (page->child->child) {
            page->child->child->kill();
        }
    }
}

该过程主要分为两步:

小结


觉得有用,请帮忙点亮红心


Better Late Than Never!
努力是为了当机会来临时不会错失机会。
共勉!

上一篇 下一篇

猜你喜欢

热点阅读