本文首發於我的博客html
文章開始以前,先想一想下面三種場景,分別輸出什麼呢?c++
注意str
的長度不能過短git
注意str
的長度不能過短github
注意str
的長度不能過短objective-c
@interface ViewController ()
{
__weak NSString *string_weak;
}
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 場景一
// NSString *str = [NSString stringWithFormat:@"https://ityongzhen.github.io/"];
// string_weak = str;
// 場景二
// @autoreleasepool {
// NSString *str = [NSString stringWithFormat:@"https://ityongzhen.github.io/"];
// string_weak = str;
// }
//
// // 場景三
NSString *str = nil;
@autoreleasepool {
str = [NSString stringWithFormat:@"https://ityongzhen.github.io/"];
string_weak = str;
}
NSLog(@"string: %@ %s", string_weak,__func__);
}
- (void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
NSLog(@"string: %@ %s", string_weak,__func__);
}
- (void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
NSLog(@"string: %@ %s", string_weak,__func__);
}
複製代碼
這個問題,暫時先放下,繼續往下看。bash
autoreleasepool
生成c++文件有以下代碼數據結構
@autoreleasepool {
NSObject *obj = [[NSObject alloc] init];
}
複製代碼
執行命令xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp
生成c++文件,其對應的代碼以下所示。app
{ __AtAutoreleasePool __autoreleasepool;
NSObject *obj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
}
複製代碼
簡化一下也就是less
{
__AtAutoreleasePool __autoreleasepool;
NSObject *obj = [[NSObject alloc] init];
}
複製代碼
其中__AtAutoreleasePool
是什麼呢?這是一個結構體,其內容以下,包含一個構造函數,在建立結構體的時候調用。一個析構函數,在結構體銷燬的時候調用。iphone
struct __AtAutoreleasePool {
//構造函數,在建立結構體的時候調用
__AtAutoreleasePool() {
atautoreleasepoolobj = objc_autoreleasePoolPush();
}
//析構函數,在結構體銷燬的時候調用
~__AtAutoreleasePool(){
objc_autoreleasePoolPop(atautoreleasepoolobj);
}
void * atautoreleasepoolobj;
};
複製代碼
因此,放在一塊兒就是在開始的時候調用 objc_autoreleasePoolPush()
結束時候調用objc_autoreleasePoolPop(atautoreleasepoolobj)
struct __AtAutoreleasePool {
//構造函數,在建立結構體的時候調用
__AtAutoreleasePool() {
atautoreleasepoolobj = objc_autoreleasePoolPush();
}
// 寫在autoreleasepool內的代碼
NSObject *obj = [[NSObject alloc] init];
//析構函數,在結構體銷燬的時候調用
~__AtAutoreleasePool(){
objc_autoreleasePoolPop(atautoreleasepoolobj);
}
void * atautoreleasepoolobj;
};
複製代碼
AutoreleasePoolPage
具體源碼能夠再Runtime源碼中查看,從源碼能夠看到objc_autoreleasePoolPush()
和objc_autoreleasePoolPop
void *
objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push();
}
void
objc_autoreleasePoolPop(void *ctxt)
{
AutoreleasePoolPage::pop(ctxt);
}
複製代碼
也就是說,這兩個函數都是操做AutoreleasePoolPage
來實現的。
類AutoreleasePoolPage
中代碼較多,篩選出主要代碼以下
class AutoreleasePoolPage
{
magic_t const magic;
id *next;
pthread_t const thread;
AutoreleasePoolPage * const parent;
AutoreleasePoolPage *child;
uint32_t const depth;
uint32_t hiwat;
id * begin() {
return (id *) ((uint8_t *)this+sizeof(*this));
}
id * end() {
return (id *) ((uint8_t *)this+SIZE);
}
bool empty() {
return next == begin();
}
bool full() {
return next == end();
}
bool lessThanHalfFull() {
return (next - begin() < (end() - begin()) / 2);
}
...
}
複製代碼
能夠看出
AutoreleasePoolPage對象經過雙向鏈表的形式鏈接在一塊兒
其中
#define I386_PGBYTES 4096 /* bytes per 80386 page */
#define PAGE_SIZE I386_PGBYTES
static size_t const SIZE = PAGE_MAX_SIZE
id * begin() {
return (id *) ((uint8_t *)this+sizeof(*this));
}
id * end() {
return (id *) ((uint8_t *)this+SIZE);
}
複製代碼
從上面的源碼中能夠看出來
AutoreleasePoolPage
有是4096字節,begin
指向的是開始存放autorelease對象
的地方,end指向結尾的位置
若是一個AutoreleasePoolPage
存不下了,就會再建立一個AutoreleasePoolPage對象
,第一個AutoreleasePoolPage對象
的child
指向第二個AutoreleasePoolPage對象
,第二個AutoreleasePoolPage對象
的parent
指向第一個AutoreleasePoolPage對象
。圖形表示就是以下
push
、pop
、autorelease
AutoreleasePoolPage
裏面有push
和pop
函數
調用push
方法會將一個POOL_BOUNDARY
入棧,而且返回其存放的內存地址
調用pop
方法時傳入一個POOL_BOUNDARY
的內存地址,會從最後一個入棧的對象開始發送release消息,直到遇到這個POOL_BOUNDARY
id *next
指向了下一個能存放autorelease
對象地址的區域
push
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;
}
複製代碼
static inline id *autoreleaseFast(id obj)
{
AutoreleasePoolPage *page = hotPage();
if (page && !page->full()) {//page沒有滿,就把obj對象加到page
return page->add(obj);
} else if (page) {//page滿了 建立新的page
return autoreleaseFullPage(obj, page);
} else {
return autoreleaseNoPage(obj);
}
}
複製代碼
pop
static inline void pop(void *token)
{
AutoreleasePoolPage *page;
id *stop;
if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
// Popping the top-level placeholder pool.
if (hotPage()) {
// Pool was used. Pop its contents normally.
// Pool pages remain allocated for re-use as usual.
pop(coldPage()->begin());
} else {
// Pool was never used. Clear the placeholder.
setHotPage(nil);
}
return;
}
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 (PrintPoolHiwat) printHiwat();
page->releaseUntil(stop);
// 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();
}
}
}
複製代碼
autorelease
inline id
objc_object::autorelease()
{
if (isTaggedPointer()) return (id)this;
// 調用rootAutorelease
if (fastpath(!ISA()->hasCustomRR())) return rootAutorelease();
return ((id(*)(objc_object *, SEL))objc_msgSend)(this, SEL_autorelease);
}
複製代碼
rootAutorelease
調用rootAutorelease2
inline id
objc_object::rootAutorelease()
{
if (isTaggedPointer()) return (id)this;
if (prepareOptimizedReturn(ReturnAtPlus1)) return (id)this;
return rootAutorelease2();
}
複製代碼
rootAutorelease2
調用autorelease
__attribute__((noinline,used))
id
objc_object::rootAutorelease2()
{
assert(!isTaggedPointer());
return AutoreleasePoolPage::autorelease((id)this);
}
複製代碼
autorelease
static inline id autorelease(id obj)
{
assert(obj);
assert(!obj->isTaggedPointer());
//再次調用autoreleaseFast
id *dest __unused = autoreleaseFast(obj);
assert(!dest || dest == EMPTY_POOL_PLACEHOLDER || *dest == obj);
return obj;
}
複製代碼
POOL_BOUNDARY
上面的源碼能夠發現POOL_BOUNDARY
是個很重要的角色,至關於一個哨兵,
objc_autoreleasePoolPush
調用時,runtime向當前的AutoreleasePoolPage
中add進一個哨兵對象(POOL_BOUNDARY),值爲0(也就是個nil)objc_autoreleasePoolPush
的返回值正是這個哨兵對象的地址,被objc_autoreleasePoolPop
(哨兵對象)做爲入參,因而
- release
消息,並向回移動next指針到正確位置@autoreleasepool
的嵌套若是多個@autoreleasepool
嵌套會怎麼樣呢?
源碼中有以下代碼
void
_objc_autoreleasePoolPrint(void)
{
AutoreleasePoolPage::printAll();
}
static void printAll()
{
_objc_inform("##############");
_objc_inform("AUTORELEASE POOLS for thread %p", pthread_self());
AutoreleasePoolPage *page;
ptrdiff_t objects = 0;
for (page = coldPage(); page; page = page->child) {
objects += page->next - page->begin();
}
_objc_inform("%llu releases pending.", (unsigned long long)objects);
if (haveEmptyPoolPlaceholder()) {
_objc_inform("[%p] ................ PAGE (placeholder)",
EMPTY_POOL_PLACEHOLDER);
_objc_inform("[%p] ################ POOL (placeholder)",
EMPTY_POOL_PLACEHOLDER);
}
else {
for (page = coldPage(); page; page = page->child) {
page->print();
}
}
_objc_inform("##############");
}
複製代碼
也就是說_objc_autoreleasePoolPrint
函數能夠用來打印一些日誌
@autoreleasepool
extern void _objc_autoreleasePoolPrint(void);
int main(int argc, char * argv[]) {
@autoreleasepool {
_objc_autoreleasePoolPrint();
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
複製代碼
輸出以下,只有一個哨兵對象(POOL)
objc[32644]: ##############
objc[32644]: AUTORELEASE POOLS for thread 0x11b111d40
objc[32644]: 3 releases pending.
objc[32644]: [0x7fcabf802000] ................ PAGE (hot) (cold)
objc[32644]: [0x7fcabf802038] 0x600003f70500 __NSArrayI
objc[32644]: [0x7fcabf802040] 0x600000950f00 __NSSetI
objc[32644]: [0x7fcabf802048] ################ POOL 0x7fcabf802048
objc[32644]: ##############
複製代碼
@autoreleasepool
若是有三個@autoreleasepool
呢?
extern void _objc_autoreleasePoolPrint(void);
int main(int argc, char * argv[]) {
@autoreleasepool {
@autoreleasepool {
@autoreleasepool {
_objc_autoreleasePoolPrint();
}
}
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
複製代碼
輸出以下,有三個POOL
,說明有三個哨兵。
objc[32735]: ##############
objc[32735]: AUTORELEASE POOLS for thread 0x114f02d40
objc[32735]: 5 releases pending.
objc[32735]: [0x7f91fd005000] ................ PAGE (hot) (cold)
objc[32735]: [0x7f91fd005038] 0x600001bbd380 __NSArrayI
objc[32735]: [0x7f91fd005040] 0x600002da4eb0 __NSSetI
objc[32735]: [0x7f91fd005048] ################ POOL 0x7f91fd005048
objc[32735]: [0x7f91fd005050] ################ POOL 0x7f91fd005050
objc[32735]: [0x7f91fd005058] ################ POOL 0x7f91fd005058
objc[32735]: ##############
複製代碼
@autoreleasepool
若是上面代碼中最裏面的@autoreleasepool
退出以後再打印呢?
extern void _objc_autoreleasePoolPrint(void);
int main(int argc, char * argv[]) {
@autoreleasepool {
@autoreleasepool {
@autoreleasepool {
}
//打印的時候,最裏面的@autoreleasepool已經退出了
_objc_autoreleasePoolPrint();
}
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
複製代碼
輸出爲以下,只有兩個哨兵(POOL)
objc[32812]: ##############
objc[32812]: AUTORELEASE POOLS for thread 0x1142b1d40
objc[32812]: 4 releases pending.
objc[32812]: [0x7fe86e800000] ................ PAGE (hot) (cold)
objc[32812]: [0x7fe86e800038] 0x600003edb0c0 __NSArrayI
objc[32812]: [0x7fe86e800040] 0x6000008dd680 __NSSetI
objc[32812]: [0x7fe86e800048] ################ POOL 0x7fe86e800048
objc[32812]: [0x7fe86e800050] ################ POOL 0x7fe86e800050
objc[32812]: ##############
複製代碼
進一步說明了,
調用push方法會將一個POOL_BOUNDARY
入棧,而且返回其存放的內存地址
調用pop方法時傳入一個POOL_BOUNDARY
的內存地址,會從最後一個入棧的對象開始發送release
消息,直到遇到這個POOL_BOUNDARY
如今咱們回過頭看文章開頭的問題,應該很好回答了。
@interface ViewController ()
{
__weak NSString *string_weak;
}
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 場景一
// NSString *str = [NSString stringWithFormat:@"https://ityongzhen.github.io/"];
// string_weak = str;
// 場景二
// @autoreleasepool {
// NSString *str = [NSString stringWithFormat:@"https://ityongzhen.github.io/"];
// string_weak = str;
// }
//
// // 場景三
NSString *str = nil;
@autoreleasepool {
str = [NSString stringWithFormat:@"https://ityongzhen.github.io/"];
string_weak = str;
}
NSLog(@"string: %@ %s", string_weak,__func__);
}
- (void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
NSLog(@"string: %@ %s", string_weak,__func__);
}
- (void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
NSLog(@"string: %@ %s", string_weak,__func__);
}
複製代碼
// 場景一
iOS定時器[24714:332118] string: https://ityongzhen.github.io/ -[ViewController viewDidLoad]
iOS定時器[24714:332118] string: https://ityongzhen.github.io/ -[ViewController viewWillAppear:]
iOS定時器[24714:332118] string: (null) -[ViewController viewDidAppear:]
場景二
iOS定時器[24676:331168] string: (null) -[ViewController viewDidLoad]
iOS定時器[24676:331168] string: (null) -[ViewController viewWillAppear:]
iOS定時器[24676:331168] string: (null) -[ViewController viewDidAppear:]
場景三
iOS定時器[24505:328544] string: https://ityongzhen.github.io/ -[ViewController viewDidLoad]
iOS定時器[24505:328544] string: (null) -[ViewController viewWillAppear:]
iOS定時器[24505:328544] string: (null) -[ViewController viewDidAppear:]
複製代碼
當使用 [NSString stringWithFormat:@"https://ityongzhen.github.io/"]
建立一個對象時,這個對象的引用計數爲 1 ,而且這個對象被系統自動添加到了當前的 autoreleasepool 中。當使用局部變量 str 指向這個對象時,這個對象的引用計數 +1 ,變成了 2 。由於在 ARC 下 NSString *str
本質上就是 __strong NSString *str
。因此在 viewDidLoad
方法返回前,這個對象是一直存在的,且引用計數爲 2 。而當viewDidLoad
方法返回時,局部變量 str
被回收,指向了 nil 。所以,其所指向對象的引用計數 -1 ,變成了 1 。
而在 viewWillAppear
方法中,咱們仍然能夠打印出這個對象的值,在viewDidAppear
方法中,這個值爲空,這個就要牽扯到RunLoop的知識了。詳解RunLoop之源碼分析一文講述了RunLoop的底層,這裏說一下,咱們的iOS處理事件是以RunLoop一直循環執行的。viewDidLoad
和viewWillAppear
在同一個RunLoop循環中,因此在 viewWillAppear
方法中,咱們仍然能夠打印出這個對象的值,可是viewDidLoad
的時候,那個RunLoop循環已經執行完了,這個對象才被完全的釋放。
當經過 [NSString stringWithFormat:@"https://ityongzhen.github.io/"]
建立一個對象時,這個對象的引用計數爲 1 。而當使用局部變量 str 指向這個對象時,這個對象的引用計數 +1 ,變成了 2 。而出了當前做用域時,局部變量 str 變成了 nil ,因此其所指向對象的引用計數變成 1 。另外,咱們知道當出了 @autoreleasepool {}
的做用域時,當前 autoreleasepool
被 drain ,其中的 autoreleased 對象被 release 。因此這個對象的引用計數變成了 0 ,對象最終被釋放
當出了 @autoreleasepool {}
的做用域時,其中的 autoreleased
對象被 release ,對象的引用計數變成 1 。當出了局部變量 str 的做用域,即 viewDidLoad
方法返回時,str 指向了 nil ,其所指向對象的引用計數變成 0 ,對象最終被釋放
前面說了注意str
的長度不能過短是爲何呢?
是由於若是str
太短。例如
@interface ViewController ()
{
__weak NSString *string_weak;
}
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 場景一
// NSString *str = [NSString stringWithFormat:@"abc"];
// string_weak = str;
// 場景二
// @autoreleasepool {
// NSString *str = [NSString stringWithFormat:@"abc"];
// string_weak = str;
// }
//
// // 場景三
NSString *str = nil;
@autoreleasepool {
str = [NSString stringWithFormat:@"abc"];
string_weak = str;
}
NSLog(@"string: %@ %s", string_weak,__func__);
}
- (void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
NSLog(@"string: %@ %s", string_weak,__func__);
}
- (void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
NSLog(@"string: %@ %s", string_weak,__func__);
}
複製代碼
結果以下:
// 場景一
iOS定時器[24714:332118] string: abc -[ViewController viewDidLoad]
iOS定時器[24714:332118] string: abc -[ViewController viewWillAppear:]
iOS定時器[24714:332118] string: abc -[ViewController viewDidAppear:]
場景二
iOS定時器[24676:331168] string: abc -[ViewController viewDidLoad]
iOS定時器[24676:331168] string: abc -[ViewController viewWillAppear:]
iOS定時器[24676:331168] string: abc -[ViewController viewDidAppear:]
場景三
iOS定時器[24505:328544] string: abc -[ViewController viewDidLoad]
iOS定時器[24505:328544] string: abc -[ViewController viewWillAppear:]
iOS定時器[24505:328544] string: abc -[ViewController viewDidAppear:]
複製代碼
這是由於,字符串的abc
採用的是Tagged Pointer技術,不是一個標準的OC對象。不存在說再堆上開闢空間存儲對象什麼的。關於Tagged Pointer能夠參考這篇文章iOS中的引用計數,這裏不作贅述。
自動釋放池的主要底層數據結構是:__AtAutoreleasePool
、AutoreleasePoolPage
調用了autorelease
的對象最終都是經過AutoreleasePoolPage
對象來管理的
每一個AutoreleasePoolPage
對象佔用4096字節內存,除了用來存放它內部的成員變量,剩下的空間用來存放autorelease
對象的地址
全部的AutoreleasePoolPage
對象經過雙向鏈表的形式鏈接在一塊兒
調用push
方法會將一個POOL_BOUNDARY
入棧,而且返回其存放的內存地址
調用pop
方法時傳入一個POOL_BOUNDARY
的內存地址,會從最後一個入棧的對象開始發送release
消息,直到遇到這個POOL_BOUNDARY
id *next
指向了下一個能存放autorelease
對象地址的區域
iOS在主線程的Runloop
中註冊了2個Observer
Observer
監聽了kCFRunLoopEntry
事件,會調用objc_autoreleasePoolPush()
Observer
kCFRunLoopBeforeWaiting
事件,會調用objc_autoreleasePoolPop()
、objc_autoreleasePoolPush()
kCFRunLoopBeforeExit
事件,會調用objc_autoreleasePoolPop()
在當次RunLoop將要結束的時候,調用objc_autoreleasePoolPop()
Objective-C Autorelease Pool 的實現原理
更多資料,歡迎關注我的公衆號,不定時分享各類技術文章。