探究Autorelease本质
一、先创建一个Dog
类,重写dealloc
方法,看看Dog
类的实例什么时候释放。工程改为MRC 工程:
文章图片
image.png 【探究Autorelease本质】
文章图片
image.png 发现实例并没有释放,在MRC 下需要添加 autorelease
或则 release
文章图片
image.png 二、c++ 重写 main.m
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m -o main-arm64.cpp
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
Dog *g = ((Dog *(*)(id, SEL))(void *)objc_msgSend)((id)((Dog *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Dog"), sel_registerName("alloc")), sel_registerName("init"));
}
return 0;
}
struct __AtAutoreleasePool {
__AtAutoreleasePool() {//构造函数,在创建结构体时调用
atautoreleasepoolobj = objc_autoreleasePoolPush();
}
~__AtAutoreleasePool() {//析构函数,在销毁结构体时调用
objc_autoreleasePoolPop(atautoreleasepoolobj);
}
void * atautoreleasepoolobj;
};
1.
__AtAutoreleasePool() { atautoreleasepoolobj = objc_autoreleasePoolPush();
}
:构造函数,在创建结构体时调用2.
~__AtAutoreleasePool() { objc_autoreleasePoolPop(atautoreleasepoolobj);
}
:析构函数,在销毁结构体时调用3.所以上面代码可以改为:
伪代码
{
__AtAutoreleasePool __autoreleasepool;
atautoreleasepoolobj = objc_autoreleasePoolPush();
Dog *g = [[[Dog alloc] init] autorelease];
objc_autoreleasePoolPop(atautoreleasepoolobj);
}
__autoreleasepool
是一个局部变量,大括号结束就会调用objc_autoreleasePoolPop
,将autorelease
对象释放。三、看源码实现
void *
objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push();
}void
objc_autoreleasePoolPop(void *ctxt)
{
AutoreleasePoolPage::pop(ctxt);
}
class AutoreleasePoolPage
{
PAGE_MAX_SIZE;
magic_t const magic;
id *next;
pthread_t const thread;
AutoreleasePoolPage * const parent;
AutoreleasePoolPage *child;
uint32_t const depth;
uint32_t hiwat;
}
以上源码只保留重要部分。
文章图片
704C76E6-6322-4DD3-9751-FA296AB96C79.png 1.AutoreleasePoolPage 本质是这么一个结构,大小是4096字节。(
PAGE_MAX_SIZE
:4096,源码得到)。2.前7个变量都是8字节,剩下的4040字节存储着
autorelease
对象地址3.
AutoreleasePoolPage * const parent;
AutoreleasePoolPage *child;
:由此可知当4040字节无法放下autorelease
对象地址时,会扩展一个AutoreleasePoolPage
,且该数据结构是,双向链表文章图片
54B8E5AE-2A59-4604-8CC1-20FE67033480.png
4.next 指针指向当前
autorelease
对象地址位置,每push 一个 对象,next 就会++。
id *add(id obj)
{
assert(!full());
unprotect();
id *ret = next;
// faster than `return next-1` because of aliasing
*next++ = obj;
protect();
return ret;
}
5.
POOL_BOUNDARY
边界:# define POOL_BOUNDARY nil
。比如:
@ autoreleasepool{
obj2
@ autoreleasepool{
obj1
}
}
每次添加一个
@ autoreleasepool
,就相当于push一次POOL_BOUNDARY
,出大括号,就pop到最近的POOL_BOUNDARY
,也就是obj1 被pop 了。接着obj2 离开大括号,也就是pop到最近的POOL_BOUNDARY
操作。嵌套的AutoreleasePool:pop的时候总会释放到上次push的位置为止,多层的pool就是多个哨兵对象而已。
static inline void *push()
{
id *dest;
if (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;
}
每次push 对象的时候会先push
POOL_BOUNDARY
边界。id * begin() {
return (id *) ((uint8_t *)this+sizeof(*this));
}id * end() {
return (id *) ((uint8_t *)this+SIZE);
}
begin:7个成员变量之后的开始位置
end:结束位置
7.
releaseUntil(begin());
:release 直到 beginhotPage()
:当前AutoreleasePoolPagecoldPage
:非当前AutoreleasePoolPage四、runloop 、 autorelease、子线程
1.主线程默认为我们开启 Runloop,Runloop 会自动帮我们创建Autoreleasepool,并进行Push、Pop 等操作来进行内存管理。
2.子线程默认不开启runloop,当产生autorelease对象时候,会将对象添加到AutoreleasePoolPage中,也就是不手动创建Autoreleasepool也能正确释放对象,线程销毁时release对象
3.自定义的 NSOperation 和 NSThread 需要手动创建自动释放池。比如: 自定义的 NSOperation 类中的 main 方法里就必须添加自动释放池。否则出了作用域后,自动释放对象会因为没有自动释放池去处理它,而造成内存泄露。
但对于 blockOperation 和 invocationOperation 这种默认的Operation ,系统已经帮我们封装好了,不需要手动创建自动释放池。
4.AutoreleasePool是按线程一一对应的(结构中的thread指针指向当前线程),没开一个线程,会有与之对应的AutoreleasePool
static inline id autorelease(id obj)
{
assert(obj);
assert(!obj->isTaggedPointer());
id *dest __unused = autoreleaseFast(obj);
assert(!dest||dest == EMPTY_POOL_PLACEHOLDER||*dest == obj);
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 __attribute__((noinline))
id *autoreleaseNoPage(id obj)
{
// "No page" could mean no pool has been pushed
// or an empty placeholder pool has been pushed and has no contents yet
assert(!hotPage());
bool pushExtraBoundary = false;
if (haveEmptyPoolPlaceholder()) {
// We are pushing a second pool over the empty placeholder pool
// or pushing the first object into the empty placeholder pool.
// Before doing that, push a pool boundary on behalf of the pool
// that is currently represented by the empty placeholder.
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",
pthread_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();
}// We are pushing an object or a non-placeholder'd pool.// Install the first page.
AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
setHotPage(page);
// Push a boundary on behalf of the previously-placeholder'd pool.
if (pushExtraBoundary) {
page->add(POOL_BOUNDARY);
}// Push the requested object or pool.
return page->add(obj);
}
Autoreleasepool 销毁时机也就是autorelease 对象销毁时机
推荐阅读
- unity探究UGUI的Image中sprite和overrideSprite的区别
- 浅谈教育与医学之本质和医学生培养之三观
- 社交本质是什么
- Objective-c
- 人际关系的本质
- 生命致简|还原生命的本质及认知生命的极限
- python|oeasy教您玩转python - 007 - # 字符本质
- Throttle|Throttle 和 Debounce 的本质及一个简单的实现
- Kotlin协程
- 心灵的救赎