总资料
全是随笔 笔记 与 学习资料。没有规律。
自动释放池
class AutoreleasePoolPage : private AutoreleasePoolPageData
继承与AutoreleasePoolPageData
, 经过综合后的主要结构为:class AutoreleasePoolPage
{
magic_t const magic;
__unsafe_unretained id *next; //能够存储的 obj 的位置的 指针
pthread_t const thread; //线程
AutoreleasePoolPage * const parent; //前一page
AutoreleasePoolPage *child;// 后一 page
uint32_t const depth;
uint32_t hiwat;
static void * operator new(size_t size) {
return malloc_zone_memalign(malloc_default_zone(), SIZE, SIZE);
}
static void operator delete(void * p) {
return free(p);
}
}
通过上面可以看出,
main.m 的源代码代码:
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
// Setup code that might create autoreleased objects goes here.
appDelegateClassName = NSStringFromClass([AppDelegate class]);
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
我们将这个文件通过以下命令
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm63.cpp
来转换成cpp文件,然后节选出关键代码为:
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
appDelegateClassName = NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class")));
}
return UIApplicationMain(argc, argv, __null, appDelegateClassName);
}
//__AtAutoreleasePool 为C++的结构体。
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;
};
通过上面转换的代码可以看出。
@autoreleasepool
包含的括号里面,会自动在最开始的时候,初始化一个 __AtAutoreleasePool
的C++的局部结构体变量。
然后在初始化这个结构体的时候,会在初始化方法里面,调用C的objc_autoreleasePoolPush()
方法,将返回的void *atautoreleasepoolobj
指针存起来。
然后在方法的大括号结束的时候,C++的局部变量会由于出栈导致 被释放,进入自己的析构方法,会触发 objc_autoreleasePoolPop(atautoreleasepoolobj)
方法。
在push的时候插入哨兵对象nil,然后返回哨兵对象的地址。 在析构的时候析构到 保存起来的地址为止
void * objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push();
}
# define POOL_BOUNDARY nil
static inline void *push()
{
id *dest;
if (slowpath(DebugPoolAllocation)) {
// Each autorelease pool starts on a new pool page.
//初始化一个page
//POOL_BOUNDARY 其实就是最开始的标志位的 nil 指针
dest = autoreleaseNewPage(POOL_BOUNDARY);
} else {
dest = autoreleaseFast(POOL_BOUNDARY);
}
ASSERT(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
return dest;
}
static __attribute__((noinline)) id *autoreleaseNewPage(id obj)
{
AutoreleasePoolPage *page = hotPage(); //hot page 表示当前激活的page
if (page) return autoreleaseFullPage(obj, page);
else return autoreleaseNoPage(obj);
}
static inline id *autoreleaseFast(id obj)
{
AutoreleasePoolPage *page = hotPage();
if (page && !page->full()) {
return page->add(obj); //将obj 加入到page 里面
} else if (page) {
return autoreleaseFullPage(obj, page);
} else {
return autoreleaseNoPage(obj);
}
}
// 将 obj 真正加入到 page 的具体位置
id *add(id obj)
{
ASSERT(!full());
unprotect();
id *ret;
#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS
...... 忽略
#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;
}
void objc_autoreleasePoolPop(void *ctxt)
{
AutoreleasePoolPage::pop(ctxt);
}
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) { //不是最开始的 标志位 nil
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);
}
//pop page 的 token 对象
return popPage<false>(token, page, stop);
}
template<bool allowDebug>
static void
popPage(void *token, AutoreleasePoolPage *page, id *stop)
{
if (allowDebug && PrintPoolHiwat) printHiwat();
//page 开始释放,直到遇到 stop 对象
page->releaseUntil(stop);
// 后面的都为 删除后,处理 page的操作
// memory: delete empty children
if (allowDebug && DebugPoolAllocation && page->empty()) {
// special case: delete everything during page-per-pool debugging
AutoreleasePoolPage *parent = page->parent;
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->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();
}
}
}
//挨个释放,直到遇到 stop 为止
void releaseUntil(id *stop)
{
// Not recursive: we don't want to blow out the stack
// if a thread accumulates a stupendous amount of garbage
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
while (page->empty()) {
page = page->parent;
setHotPage(page);
}
page->unprotect();
#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
}
在 @autoreleasepool { }
包裹的里面,对象在初始化的时候 会自动调用 - (id)autorelease
方法, 所以能在 pool::pop()的时候一并被释放。
源码:
- (id)autorelease {
return _objc_rootAutorelease(self);
}
id
_objc_rootAutorelease(id obj)
{
ASSERT(obj);
return obj->rootAutorelease();
}
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()
{
ASSERT(!isTaggedPointer());
return AutoreleasePoolPage::autorelease((id)this);
}
static inline id autorelease(id obj)
{
ASSERT(!obj->isTaggedPointerOrNil());
id *dest __unused = autoreleaseFast(obj);
#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS
ASSERT(!dest || dest == EMPTY_POOL_PLACEHOLDER || (id)((AutoreleasePoolEntry *)dest)->ptr == obj);
#else
ASSERT(!dest || dest == EMPTY_POOL_PLACEHOLDER || *dest == obj);
#endif
return obj;
}
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);
}
}
我们可以在上面看到 最后也进入了static inline id *autoreleaseFast(id obj)
方法,相当于进入了上一小节里面的 push 方法。 所以会将当前创建的 obj 对象加入到relase page 里面去。
如下,没有被@autoreleasepool { }
直接包裹的对象,会在什么时候释放呢?
- (void)viewDidLoad {
[super viewDidLoad];
MYPerson *person = [[MYPerson alloc] init];
}
其实并不是在 }
这里进行释放,而是和RunLoop
有关。
我们修改打印一下代码。
NSLog(@"%@", [NSRunLoop currentRunLoop]);
直接调用私有方法, 可以打印出runloop (自己在Xcode13.2.1 打印后并未找到下面的 observe。 有知道原因的么?)
<CFRunLoopObserver 0x600001d145a0 [0x7fff80617cb0]>{
valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647,
callout = _wrapRunLoopWithAutoreleasePoolHandler (0x7fff4808bf54),
context = <CFArray 0x6000022454a0 [0x7fff80617cb0]>{
type = mutable-small, count = 1, values = (
0 : <0x7fabae001038>
)}}
<CFRunLoopObserver 0x600001d14640 [0x7fff80617cb0]>{
valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647,
callout = _wrapRunLoopWithAutoreleasePoolHandler (0x7fff4808bf54), context = <CFArray 0x6000022454a0 [0x7fff80617cb0]>{
type = mutable-small, count = 1, values = (
0 : <0x7fabae001038>
)}}
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0),
kCFRunLoopBeforeTimers = (1UL << 1),
kCFRunLoopBeforeSources = (1UL << 2),
kCFRunLoopBeforeWaiting = (1UL << 5),
kCFRunLoopAfterWaiting = (1UL << 6),
kCFRunLoopExit = (1UL << 7),
kCFRunLoopAllActivities = 0x0FFFFFFFU
};
activities = 0x1
== kCFRunLoopEntry
, 代表runloop 进入的时候
activities = 0xa0
== kCFRunLoopExit
+ kCFRunLoopBeforeWaiting
, 代表开始等待和退出runloop
activities = 0x1
会在包含自己的自动释放池(如果没有手动包含,那么就会说main文件的自动释放池)里面, 进行Push操作,将哨兵对象(边界对象)POOL_BOUNDARY
压入栈中。
activities = 0xa0
的callout
方法,会先进行 pop
, 然后在进行push
一个哨兵对象。exit
的时候只会进行pop
。 也就是说pop
的时候会把上个循环push
后面的所有能自动释放的对象都进行释放。
最终顺序为:Entry
--> push
--> beforeWait
—> pop
--> push
--> …多个beforewait
… —> exit
--> pop
。 保证push 和 pop成对出现。 在每次循环的时候清空 能够释放的对象。
@autoreleasepool { }
能够嵌套使用。可以存在N个page, 每个page 4096=4K个大小,是个双向链表@autoreleasepool
是c++的结构体。
POOL_BOUNDARY
压入push
栈中作为标识{}
中间的 调用了-autorelase()
方法的对象,会push
到栈中,位于POOL_BOUNDARY
之后}
的时候会调用析构函数,会pop
对象,直到遇到POOL_BOUNDARY
标志为止。push
、pop
方法。@autoreleasepool
的情况就是综合上面 的case进行释放。主要用于for循环或者其他内部代码包含有大量内存的,能够保证及时释放。