這篇文章其實(shí)是深入內(nèi)存管理:從所有權(quán)修飾符開始的補(bǔ)充。因?yàn)橛捎?code>__autoreleasing的試驗(yàn)過于多,都寫在上一篇文章中會(huì)使得文章篇幅結(jié)構(gòu)很難看,所以在這里新建一篇文章來記錄。
方法介紹
下面需要介紹兩個(gè)方法:
1._objc_rootRetainCount(id obj)方法,作用是返回obj的引用計(jì)數(shù)。
2._objc_autoreleasePoolPrint()方法,作用是打印當(dāng)前的自動(dòng)釋放池對象。
使用方法:
直接定義在類中就可以使用
extern uintptr_t _objc_rootRetainCount(id obj);
extern void _objc_autoreleasePoolPrint(void);
試驗(yàn)開始
一、基礎(chǔ)試驗(yàn)
下面有三個(gè)小實(shí)驗(yàn),為了證明結(jié)論:
在取得非自己生成并持有的對象時(shí),編譯器會(huì)默認(rèn)把對象注冊到自動(dòng)釋放池中。
也就是說:
編譯器為判斷方法名是否是以
alloc/new/copy/mutableCopy開頭,如果不是,就自動(dòng)將返回的對象注冊到池子中。
1. 直接alloc方法初始化
代碼:
id __weak obj0;
{
id obj1 = [[NSMutableArray alloc] init];
obj0 = obj1;
NSLog(@"%p", obj0);
NSLog(@"%lu", _objc_rootRetainCount(obj0));
_objc_autoreleasePoolPrint();
}
NSLog(@"obj0-1-%@", obj0);
實(shí)驗(yàn)結(jié)果:
程序到最后一行時(shí)輸出為空。
輸出:

分析:
這里其實(shí)很簡單,編譯器的模擬代碼為
id obj1 = objc_msgSend(NSMutableArray, @selector(alloc));
objc_msgSend(obj1, @selector(init));
objc_release(obj1);
當(dāng)變量obj1的作用域消失時(shí),編譯器會(huì)自動(dòng)插入objc_release(obj1);。這里并沒有涉及到自動(dòng)釋放池。所以array對象會(huì)被自動(dòng)銷毀。
2. 用array方法初始化
代碼
id __weak obj0;
{
id obj2 = [NSMutableArray array];
obj0 = obj2;
NSLog(@"%p", obj2);
NSLog(@"%lu", _objc_rootRetainCount(obj2));
_objc_autoreleasePoolPrint();
}
NSLog(@"obj0-2-%@", obj0);
實(shí)驗(yàn)結(jié)果:


分析:
這里發(fā)現(xiàn)obj2的引用計(jì)數(shù)為2,再看自動(dòng)釋放池最后一個(gè)對象類型為__NSArrayM,正是obj2持有的對象。說明該對象已經(jīng)加入到了自動(dòng)釋放池中,所以最后輸出有值。那么這個(gè)對象什么時(shí)候釋放呢,我們后面說。
3. 用array方法初始化 并自己添加池子
代碼:
@autoreleasepool {
id obj3 = [NSMutableArray array];
obj0 = obj3;
NSLog(@"%p", obj3);
NSLog(@"%lu", _objc_rootRetainCount(obj3));
_objc_autoreleasePoolPrint();
}
NSLog(@"obj0-3-%@", obj0);
實(shí)驗(yàn)結(jié)果:


分析:
雖然這里和實(shí)驗(yàn)2一樣被放入到了池子中,但是最后打印還是為空。說明在退出池子后,對象被銷毀了。
4.總結(jié)
以上三個(gè)實(shí)驗(yàn)驗(yàn)證了之前提出的結(jié)論。
下面以第三個(gè)實(shí)驗(yàn)作為例子分析代碼:
@autoreleasepool {
// [NSMutableArray array]返回的對象會(huì)被默認(rèn)加入到池子中
// 對象的引用計(jì)數(shù)為1
// obj3默認(rèn)為__strong,強(qiáng)引用array對象
// array對象的引用計(jì)數(shù)為2
id obj3 = [NSMutableArray array];
// obj0對__weak 因此對象引用計(jì)數(shù)不變
obj0 = obj3;
NSLog(@"%p", obj3);
NSLog(@"%lu", _objc_rootRetainCount(obj3));
_objc_autoreleasePoolPrint();
}
// obj3的作用域結(jié)束,釋放對象 計(jì)數(shù)-1
// 池子結(jié)束 池子中的對象要被釋放 計(jì)數(shù)-1
// 對象計(jì)數(shù)為0 因此銷毀
// 輸出為空
NSLog(@"obj0-3-%@", obj0);
二、再次驗(yàn)證試驗(yàn)一
下面驗(yàn)證自己定義的方法是否也可以遵守以上結(jié)論。
1.不以alloc等開頭的方法
代碼:
{
id obj5 = [[self class] Object];
obj0 = obj5;
NSLog(@"%p", obj0);
NSLog(@"%lu", _objc_rootRetainCount(obj0));
_objc_autoreleasePoolPrint();
}
NSLog(@"obj0-5-%@", obj0);
+ (id)Object
{
id array = [[NSMutableArray alloc] init];
return array;
}
輸出:


分析:
最后輸出沒有問題,確實(shí)是加入到了池子中。但是為什么這里的計(jì)數(shù)是3呢???
2.以alloc等開頭的方法
代碼:
{
id obj6 = [[self class] allocObject];
obj0 = obj6;
NSLog(@"%p", obj0);
NSLog(@"%lu", _objc_rootRetainCount(obj0));
_objc_autoreleasePoolPrint();
}
NSLog(@"obj0-6-%@", obj0);
+ (id)allocObject
{
id array = [[NSMutableArray alloc] init];
return array;
}
輸出:


分析:
這里的引用計(jì)數(shù)變成2了,但是沒有加入到池子中,且最后輸出為空。說明這里多出的1的引用計(jì)數(shù)來自+ (id)allocObject方法。
3.總結(jié)
自定義的方法仍然可以說明實(shí)驗(yàn)一的結(jié)論。
三、何時(shí)釋放
下面定義一個(gè)屬性@property (nonatomic, weak) id obj;,并在- (void)viewDidLoad中賦值。
- (void)viewDidLoad
{
[super viewDidLoad];
id obj = [NSMutableArray array];
self.obj = obj;
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
NSLog(@"%p", self.obj);
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
NSLog(@"%p", self.obj);
}
輸出:
2017-04-28 11:23:54.829 MRCTest[57782:5898069] 0x6000000523f0
2017-04-28 11:23:54.832 MRCTest[57782:5898069] 0x0
為什么在viewWillAppear中還存在而到了viewDidAppear中就為空了?我猜測是否是執(zhí)行完viewWillAppear后,自動(dòng)釋放池銷毀了,導(dǎo)致array對象也銷毀了。
其實(shí)原因是viewDidLoad和viewWillAppear是在同一個(gè)RunLoop中調(diào)用的,而viewDidAppear與他們不是同一個(gè),因此當(dāng)RunLoop一圈結(jié)束時(shí),池子被銷毀,里面的對象也自然被銷毀了。
最后
其實(shí)我想寫一點(diǎn)__strong以及__autoreleasing底層代碼的,但是因?yàn)樽约阂矝]有完全理解,所以還是不亂寫了。