引用计数与autorealeasepoll为ios中从ARC开始提及就非常多,当一个对象的引用计数release为0时,则会被释放掉,而autoreleasepool在其中也起到了非常重要的作用
补充一下:ARC是LVVM和runtime配合的结果,其禁止了引用计数相关的方法(MRC中可以使用):retain/release/retainCount/dealloc
引用计数
引用计数过程中通过retain可以使对象引用计数+1,而release则会使对象引用计数-1,当引用计数为0时,该对象会被释放(不会想临时变量一样出栈立即释放,因此后续autoreleasepool会介绍)
对象的isa在通过指针优化,里面会存放着extra_rc、shiftcls、magic、weakly_referenced、deallocating、has_sidetable_rc等参数
下面介绍下里面的参数:
nonpointer:表示是否对 isa 指针开启指针优化 0:纯isa指针,1:不⽌是类对象地址,isa中包含了类信息、对象的引⽤用计数等
has_assoc:关联对象标志位,0没有,1存在
has_cxx_dtor:该对象是否有C++或者Objc的析构器器,如果有析构函数,则需要做析构逻辑, 如果没有,则可以更更快的释放对象
shiftcls:存储类指针的值。开启指针优化的情况下,在arm64架构中有33位⽤用来存储类指针。
magic:⽤用于调试器器判断当前对象是真的对象还是没有初始化的空间
weakly_referenced:对象是否被指向或者曾经指向⼀个ARC的弱变量量,没有弱引⽤的对象可以更快释放。
deallocating:标志对象是否正在释放内存
has_sidetable_rc:当对象引⽤用技术大于10时,则需要借⽤该变量存储进位
extra_rc:当表示该对象的引⽤用计数值,实际上是引⽤用计数值减1,例如:如果对象的引⽤用计数为10,那么extra_rc为9。如果引⽤用计数⼤大于10, 则需要使⽤用到下⾯面的has_sidetable_rc
sidetable
sidetable相信不是第一次看到,当isa优化的引用计数空间不够用时,会存放到这里面,且其不仅仅存放这里的引用计数,前面的其中一篇文章weaktable也在这里面
sidetable中包含: Spinlock_t⾃自旋锁 RefcountMap引⽤用技术、weak_table_t弱引⽤用表
而sidetable是从总的sidetables中根据对象的哈希值来获取到的,RefcountMap为引用计数哈希表,在里面通过对象的哈希值来获取引用计数对象,然后通过retain和release来操作对象的引用计数(这原理与weaktable相似)
引用计数就介绍到这里,可以探究下,对象alloc之后的引用计数是多少?retainCount方法干了什么?
autoreleasepool(自动释放池)
研究自动释放池之前,我们就看看自动释放池使用的时候做了什么
首先main函数中实现这么一段代码
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSObject *obj = [NSObject alloc];
}
return NSApplicationMain(argc, argv);
}
进入目录,通过 clang 编译代码
clang -rewrite-objc main.m -o main.c
编译后的代码如下所示:
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
NSObject *obj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc"));
}
return NSApplicationMain(argc, argv);
}
这里面编译出来的代码出现了c++的结构__AtAutoreleasePool,如下所示,其中有构造函数和析构函数(~)
struct __AtAutoreleasePool {
__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
void * atautoreleasepoolobj;
};
第一个是构造函数:即创建的时候走这个方法,像声明临时变量一样声明后,即为创建
第二个是析构函数(~):销毁的时候走这个方法,即临时变量销毁的时机,即碰到autoreleasepool的大括号}的尾部即调用析构函数,即执行完里面的任务就会调用析构函数
即可以理解为如下代码
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ {
objc_autoreleasePoolPush();
NSObject *obj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc"));
objc_autoreleasePoolPop(atautoreleasepoolobj);
}
return NSApplicationMain(argc, argv);
}
接下来查看objc源码看看autoreleasepool的结构是怎么样的
通过搜索objc_autoreleasePoolPush方法可以找到相关入口
struct AutoreleasePoolPageData
{
...
magic_t const magic;
__unsafe_unretained id *next;
pthread_t const thread;
AutoreleasePoolPage * const parent;
AutoreleasePoolPage *child;
uint32_t const depth;
uint32_t hiwat;
...
};
可以看到AutoreleasePoolPage继承自AutoreleasePoolPageData,且AutoreleasePoolPageData的参数如下,可以猜到其结构为一个双向链表(不是树,一个节点只有一个子分支),每一个节点都有前驱(parent)和后继(child),因此会有多个AutoreleasePoolPage存在
接下来通过objc_autoreleasePoolPush和objc_autoreleasePoolPop方法来分析其中原理
objc_autoreleasePoolPush
void *
objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push();
}
//加入autoreleasepull
static inline void *push()
{
id *dest;
if (slowpath(DebugPoolAllocation)) {
// Each autorelease pool starts on a new pool page.
dest = autoreleaseNewPage(POOL_BOUNDARY);
} else {
dest = autoreleaseFast(POOL_BOUNDARY);
}
ASSERT(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
return dest;
}
最后进入autoreleaseFast方法来处理
static inline id *autoreleaseFast(id obj)
{
//获取autoreleasepoolpage
AutoreleasePoolPage *page = hotPage();
if (page && !page->full()) {
//如果存在没满加入poolpage中
return page->add(obj);
} else if (page) {
//存在page,且当前page满了
return autoreleaseFullPage(obj, page);
} else {
//不存在page,创建新的page加入
return autoreleaseNoPage(obj);
}
}
有page为满的情况,autoreleaseFullPage方法如下所示
static __attribute__((noinline))
id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page)
{
// The hot page is full.
// Step to the next non-full page, adding a new page if necessary.
// Then add the object to that page.
ASSERT(page == hotPage());
ASSERT(page->full() || DebugPoolAllocation);
//往后找page,如果都满了则创建新的page
do {
if (page->child) page = page->child;
else page = new AutoreleasePoolPage(page);//创建新page
} while (page->full());
//设置新page
setHotPage(page);
//将对象加入到新的page中
return page->add(obj);
}
没有page的情况,autoreleaseNoPage实现如下所示
static __attribute__((noinline))
id *autoreleaseNoPage(id obj)
{
bool pushExtraBoundary = false;
if (haveEmptyPoolPlaceholder()) {
pushExtraBoundary = true;
}
else if (obj != POOL_BOUNDARY && DebugMissingPools) {
// We are pushing an object with no pool in place,
// and no-pool debugging was requested by environment.
_objc_inform("MISSING POOLS: (%p) Object %p of class %s "
"autoreleased with no pool in place - "
"just leaking - break on "
"objc_autoreleaseNoPool() to debug",
objc_thread_self(), (void*)obj, object_getClassName(obj));
objc_autoreleaseNoPool(obj);
return nil;
}
else if (obj == POOL_BOUNDARY && !DebugPoolAllocation) {
// We are pushing a pool with no pool in place,
// and alloc-per-pool debugging was not requested.
// Install and return the empty pool placeholder.
return setEmptyPoolPlaceholder();
}
//创建新page,并设置
AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
setHotPage(page);
// Push a boundary on behalf of the previously-placeholder'd pool.
//给第一个page加入POOL_BOUNDARY
if (pushExtraBoundary) {
page->add(POOL_BOUNDARY);
}
//加入到新的page中
return page->add(obj);
}
add方法实现如下所示,主要next指针位置保存新的obj,next指向后一位地址,还引入了AutoreleasePoolEntry,其功能与其相似
id *add(id obj)
{
ASSERT(!full());
unprotect();
id *ret;
#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS
if (!DisableAutoreleaseCoalescing || !DisableAutoreleaseCoalescingLRU) {
if (!DisableAutoreleaseCoalescingLRU) {
if (!empty() && (obj != POOL_BOUNDARY)) {
AutoreleasePoolEntry *topEntry = (AutoreleasePoolEntry *)next - 1;
for (uintptr_t offset = 0; offset < 4; offset++) {
AutoreleasePoolEntry *offsetEntry = topEntry - offset;
if (offsetEntry <= (AutoreleasePoolEntry*)begin() || *(id *)offsetEntry == POOL_BOUNDARY) {
break;
}
if (offsetEntry->ptr == (uintptr_t)obj && offsetEntry->count < AutoreleasePoolEntry::maxCount) {
if (offset > 0) {
AutoreleasePoolEntry found = *offsetEntry;
memmove(offsetEntry, offsetEntry + 1, offset * sizeof(*offsetEntry));
*topEntry = found;
}
topEntry->count++;
ret = (id *)topEntry; // need to reset ret
goto done;
}
}
}
} else {
if (!empty() && (obj != POOL_BOUNDARY)) {
AutoreleasePoolEntry *prevEntry = (AutoreleasePoolEntry *)next - 1;
if (prevEntry->ptr == (uintptr_t)obj && prevEntry->count < AutoreleasePoolEntry::maxCount) {
prevEntry->count++;
ret = (id *)prevEntry; // need to reset ret
goto done;
}
}
}
}
#endif
ret = next; // faster than `return next-1` because of aliasing
*next++ = obj;
#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS
// Make sure obj fits in the bits available for it
ASSERT(((AutoreleasePoolEntry *)ret)->ptr == (uintptr_t)obj);
#endif
done:
protect();
return ret;
}
objc_autoreleasePoolPop
objc_autoreleasePoolPop方法为释放pool内部引用的对象,实现如下所示,最终会调用popPage方法来释放pool内部对象
static inline void
pop(void *token)
{
AutoreleasePoolPage *page;
id *stop;
if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
// Popping the top-level placeholder pool.
page = hotPage();
if (!page) {
// Pool was never used. Clear the placeholder.
return setHotPage(nil);
}
// Pool was used. Pop its contents normally.
// Pool pages remain allocated for re-use as usual.
page = coldPage();
token = page->begin();
} else {
page = pageForPointer(token);
}
stop = (id *)token;
if (*stop != POOL_BOUNDARY) {
if (stop == page->begin() && !page->parent) {
// Start of coldest page may correctly not be POOL_BOUNDARY:
// 1. top-level pool is popped, leaving the cold page in place
// 2. an object is autoreleased with no pool
} else {
// Error. For bincompat purposes this is not
// fatal in executables built with old SDKs.
return badPop(token);
}
}
if (slowpath(PrintPoolHiwat || DebugPoolAllocation || DebugMissingPools)) {
return popPageDebug(token, page, stop);
}
return popPage<false>(token, page, stop);
}
//释放内部对象实际方法
template<bool allowDebug>
static void
popPage(void *token, AutoreleasePoolPage *page, id *stop)
{
if (allowDebug && PrintPoolHiwat) printHiwat();
page->releaseUntil(stop);
// memory: delete empty children
if (allowDebug && DebugPoolAllocation && page->empty()) {
// special case: delete everything during page-per-pool debugging
AutoreleasePoolPage *parent = page->parent;
//page为空,清理掉当前page
page->kill();
setHotPage(parent);
} else if (allowDebug && DebugMissingPools && page->empty() && !page->parent) {
// special case: delete everything for pop(top)
// when debugging missing autorelease pools
//page为空,清理掉当前page
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();
}
}
}
kill方法为清除掉当前page,代码如下所示
void kill()
{
// Not recursive: we don't want to blow out the stack
// if a thread accumulates a stupendous amount of garbage
AutoreleasePoolPage *page = this;
while (page->child) page = page->child;
AutoreleasePoolPage *deathptr;
do {
deathptr = page;
page = page->parent;
if (page) {
page->unprotect();
page->child = nil;
page->protect();
}
delete deathptr;
} while (deathptr != this);
}
releaseUntil方法为对释放池内的对象进行release,减少引用计数,引用计数归零的会被系统自动回收,代码如下所示
void releaseUntil(id *stop)
{
// Not recursive: we don't want to blow out the stack
// if a thread accumulates a stupendous amount of garbage
//遍历释放池page,对内部对象进行释放
while (this->next != stop) {
// Restart from hotPage() every time, in case -release
// autoreleased more objects
AutoreleasePoolPage *page = hotPage();
// fixme I think this `while` can be `if`, but I can't prove it
//从头开始找到不为空的page
while (page->empty()) {
page = page->parent;
setHotPage(page);
}
page->unprotect();
//知道page->next指向其上一位,并释放page->next指向的对象obj
#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS
AutoreleasePoolEntry* entry = (AutoreleasePoolEntry*) --page->next;
// create an obj with the zeroed out top byte and release that
id obj = (id)entry->ptr;
int count = (int)entry->count; // grab these before memset
#else
id obj = *--page->next;
#endif
memset((void*)page->next, SCRIBBLE, sizeof(*page->next));
page->protect();
if (obj != POOL_BOUNDARY) {
#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS
// release count+1 times since it is count of the additional
// autoreleases beyond the first one
for (int i = 0; i < count + 1; i++) {
objc_release(obj);
}
#else
objc_release(obj);
#endif
}
}
setHotPage(this);
#if DEBUG
// we expect any children to be completely empty
for (AutoreleasePoolPage *page = child; page; page = page->child) {
ASSERT(page->empty());
}
#endif
}
_objc_rootAutorelease
这个和autoreleasepool的一样,最后会调用到autoreleaseFast方法,经过了下面几个函数,最后到达autoreleaseFast
_objc_rootAutorelease->rootAutorelease->rootAutorelease2->autorelease->autoreleaseFast
runloop与autoreleasepool
上面讲到的是autoreleasepool的基本功能,其与runloop配合才会发挥巨大功效
runloop顾名思义是一个运行循环,其与线程绑定依赖线程而存在,基本上我们写的东西运行全靠它执行,可以参考前面的runloop
全局的autoreleasepool
runloop每一轮循环都会执行source、observer等,我们在运行过程创建的临时变量都被会捕获进入全局的autorelease中,当runloop一轮周期执行完毕之后,runloop会将自动释放池中的autorelease临时对象全部release一遍,引用计数不为零的仍然存活,引用计数清零的直接嗝屁
处理临时对象的autoreleasepool
另外我们在一个方法循环中创建的autoreleasepool,为了临时变量造成的内存过大,其代码如下所示:
for (NSInteger i = 0; i < NSIntegerMax; i++) {
@autoreleasepool {
//这里只是小对象,但数量很多,也有很大的对象更要注意
id obj = [NSObject alloc];
} //执行到这个括号时会自动调用autorelease的析构函数(学c++时应该会有介绍)
}
因此为了避免内存峰值过大,甚至可能会被系统看门狗强行kill应用,必须要使用autoreleasepool,前面也看到了autoreleasepool编译后的代码,会在当前栈区执行其构造和析构函数,会自动对此此时加入的autorelease对象进行一次释放,因此内存性能不会出现较大波动
注意:不要因为在函数栈内创建的的都是存在栈区,通过alloc创建的对象都是堆区,不会立即释放,而此时存放在栈区的obj只是一个临时的指针罢了,真实的对象在堆区,需要等到autoreleasepool对其进行release了,如果没有此处的autoreleasepool,则需要等到此轮runloop执行完毕,等到整体对新加入对象的依次release
最后
了解autoreleasepool后,可以更加清楚地理解ARC下对象的释放时机,以及更好的把控对象的释放时机
最后,内存优化的知识又多了一角