MRC
手动内存管理,每一次的retain,new,alloc都对应一次release,autorelease;
ARC
自动引用计数,由编译器在编译期间用更底层的C接口实现retain/release/autorelease,不需要手动释放。(底层的原理和接口api待会讲解)
ARC的规则
当对象创建时,引用计数为1;当一个强指针(strong或者retain)指向该对象时,引用计数+1;当强指针不在指向该对象时,引用计数-1;当对象的引用计数为0时,说明这个对象不被任何指针指向,可以销毁,回收内存。
ARC的内部实现
ARC背后的引用计数主要依赖于三个方法:
- retain:增加引用计数
- release:降低引用计数,引用计数为0的时候,释放对象
- autorelease:在当前的autorelease pool结束后,降低引用计数
下面我们来看看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执行完毕时,对对象进行释放;
- 自动释放:当runloop迭代退出的时候,自动对对象进行释放。
所以autorelease会延迟对象的release。
下面介绍一下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;
};
介绍一下参数:
- magic 检查校验完整性的变量
- next 指向新加入的autorelease对象
- thread page 当前所在的线程,AutoreleasePool是按线程一一对应的(结构中的thread指针指向当前线程)
- parent 父节点 指向前一个page
- child 子节点 指向下一个page
- depth 链表的深度,节点个数
- hiwat 数据容纳的上限(high water mark)
- EMPTY_POOL_PLACEHOLDER 空池占位
- POOL_BOUNDARY 是一个边界对象nil,之前变量名是POOL_SENTINEL哨兵对象,用来区分每个page即每个autoreleasepoolpage边界
- PAGE_MAX_SIZE = 4096
- COUNT 一个page里对象数
双向链表
自动释放池并没有单独的结构,而是由若干个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();
}
}
}
该过程主要分为两步:
- page->releaseUntil(stop),对栈顶(page->next)到stop地址(POOL_SENTINEL)之间的所有对象调用objc_release(),进行引用计数减1
- 清空page对象page->kill()
小结
- 自动释放池是多个AutoreleasePoolPage以双向链表的形式组成的
- push时会将边界对象加入AutoreleasePoolPage的栈中,作为一个标识,且返回该边界对象;
- 当对象调用autorelease方法时,会将对象加入AutoreleasePoolPage的栈中;
- pop时传入边界对象,然后对page中的对象发送release消息;
觉得有用,请帮忙点亮红心
Better Late Than Never!
努力是为了当机会来临时不会错失机会。
共勉!
网友评论