在ViewController中執(zhí)行
self.view = nil;
會發(fā)生什么?
前提是ARC的環(huán)境。
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
UIButton *tempBtn = [UIButton buttonWithType:UIButtonTypeSystem];
[tempBtn setTitle:@"測試按鈕" forState:UIControlStateNormal];
tempBtn.tag = 100;
tempBtn.frame = CGRectMake(100, 100, 100, 100);
tempBtn.backgroundColor = [UIColor cyanColor];
[tempBtn addTarget:self action:@selector(clickBtn:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:tempBtn];
NSLog(@"執(zhí)行了 --------------------------- viewDidLoad ");
}
/** 按鈕的點擊事件 */
- (void)clickBtn:(UIButton *)sender
{
TempViewController *temp = [[TempViewController alloc] init];
[self.navigationController pushViewController:temp animated:YES];
self.view = nil;
NSLog(@"sender ==== %@",sender);
NSLog(@"self.view.subViews = %@",self.view.subviews);
UIButton *btn = [self.view viewWithTag:100];
NSLog(@"btn ==== %@",btn);
}
點擊按鈕的時候執(zhí)行 self.view = nil;的操作。
發(fā)現(xiàn)程序會馬上再走一次,viewDidLoad方法。
有兩個問題:
1、self.view = nil; self.view被釋放了嗎?
2、self.view上面的subViews被釋放了嗎?
這個可以參考:http://stackoverflow.com/questions/14679475/about-self-view-nil-in-arc
大概理解,self.view之前是被Controller強(qiáng)引用的,當(dāng) self.view = nil 如果執(zhí)行完,self.view的引用計數(shù)變成0了就自然被釋放了
同樣的道理,如果self.view被釋放了,那么上面的子控件如果沒有其他的強(qiáng)引用的話,那么引用計數(shù)也會變成0,因為之前只是被self.view強(qiáng)引用了。所以也會被釋放掉。
可以用以下代碼驗證:
#import "ViewController.h"
#import "TempViewController.h"
@interface ViewController ()
@property (strong, nonatomic) UILabel *strongLabel;
@property (weak, nonatomic) UILabel *weakLabel;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
UIButton *tempBtn = [UIButton buttonWithType:UIButtonTypeSystem];
[tempBtn setTitle:@"測試按鈕" forState:UIControlStateNormal];
tempBtn.frame = CGRectMake(100, 100, 100, 100);
tempBtn.backgroundColor = [UIColor cyanColor];
[tempBtn addTarget:self action:@selector(clickBtn:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:tempBtn];
UIButton *tempBtn1 = [UIButton buttonWithType:UIButtonTypeSystem];
[tempBtn1 setTitle:@"測試按鈕1" forState:UIControlStateNormal];
tempBtn1.frame = CGRectMake(220, 100, 100, 100);
tempBtn1.backgroundColor = [UIColor cyanColor];
[tempBtn1 addTarget:self action:@selector(clickBtn1:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:tempBtn1];
NSLog(@"執(zhí)行了 --------------------------- viewDidLoad ");
UILabel *lab = [[UILabel alloc] initWithFrame:CGRectMake(100, 220, 100, 100)];
lab.backgroundColor = [UIColor redColor];
lab.tag = 300;
[self.view addSubview:lab];
NSLog(@"lab = === %@",lab);
NSLog(@"strongLabel == %@",_strongLabel);
NSLog(@"weakLabel == %@",_weakLabel);
}
/** 按鈕的點擊事件 */
- (void)clickBtn:(UIButton *)sender
{
// 獲取label
UILabel *lab = [self.view viewWithTag:300];
self.strongLabel = lab;
self.weakLabel = lab;
TempViewController *temp = [[TempViewController alloc] init];
[self.navigationController pushViewController:temp animated:YES];
}
- (void)clickBtn1:(UIButton *)sender
{
UILabel *lab = [self.view viewWithTag:300];
NSLog(@"lab = === %@",lab);
NSLog(@"strongLabel == %@",_strongLabel);
NSLog(@"weakLabel == %@",_weakLabel);
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
self.view = nil;
}
@end
首次執(zhí)行,打印結(jié)果:
執(zhí)行了 --------------------------- viewDidLoad
lab = === <UILabel:0x7fb5c9d11250; frame = (100 220; 100 100); userInteractionEnabled = NO; tag = 300; layer = <_UILabelLayer: 0x60800009c0c0>>
strongLabel == (null)
weakLabel == (null)
點擊測試按鈕1,打印結(jié)果:
lab = === <UILabel: 0x7fb5c9d11250; frame = (100 220; 100 100); userInteractionEnabled = NO; tag = 300; layer = <_UILabelLayer: 0x60800009c0c0>>
strongLabel == (null)
weakLabel == (null)
此時 strongLabel 和 weakLabel 還是null。
點擊測試按鈕,跳轉(zhuǎn)界面,
并且將lab分別強(qiáng)引用給strongLabel,弱引用weakLable。
然后在VieeController的
- (void)viewWillDisappear:(BOOL)animated方法中執(zhí)行self.view = nil;操作。
點擊返回按鈕,然后點擊測試按鈕1,查看打印結(jié)果
lab = === <UILabel: 0x7fb5c9c01720; frame = (100 220; 100 100); userInteractionEnabled = NO; tag = 300; layer = <_UILabelLayer: 0x60000009cca0>>
strongLabel == <UILabel: 0x7fb5c9d11250; frame = (100 220; 100 100); userInteractionEnabled = NO; tag = 300; layer = <_UILabelLayer: 0x60800009c0c0>>
weakLabel == <UILabel: 0x7fb5c9d11250; frame = (100 220; 100 100); userInteractionEnabled = NO; tag = 300; layer = <_UILabelLayer: 0x60800009c0c0>>
發(fā)現(xiàn),strongLabel 、weakLabel是初次label的地址,和重新生成的label地址不同。
所以總結(jié)可得。如果執(zhí)行self.view = nil操作之后,如果view上面相關(guān)的子控件被其他的類強(qiáng)引用的話,就不會被釋放,只是引用計數(shù)減一。
如果代碼修改成下面這樣:
/** 按鈕的點擊事件 */
- (void)clickBtn:(UIButton *)sender
{
// 獲取label
UILabel *lab = [self.view viewWithTag:300];
// self.strongLabel = lab;
self.weakLabel = lab;
TempViewController *temp = [[TempViewController alloc] init];
[self.navigationController pushViewController:temp animated:YES];
}
那么執(zhí)行完上面的操作之后,最后weakLabel也是nil,因為沒有強(qiáng)引用lab。
這個有什么應(yīng)用場景呢?當(dāng)然現(xiàn)在隨著iOS設(shè)備的硬件的升級,可能不會出現(xiàn)使用self.view = nil;的場景了
在之前的一些設(shè)備上的時候,內(nèi)存小,很容易就會內(nèi)存不夠用了。
移動設(shè)備終端的內(nèi)存極為有限,應(yīng)用程序必須做好low-memory處理工作,才能避免程序因內(nèi)存使用過大而崩潰。
low-memory 處理思路
通常一個應(yīng)用程序會包含多個view controllers,當(dāng)從view跳轉(zhuǎn)到另一個view時,之前的view只是不可見狀態(tài),并不會立即被清理掉,而是保存在內(nèi)存中,以便下一次的快速顯現(xiàn)。但是如果應(yīng)用程序接收到系統(tǒng)發(fā)出的low-memory warning,我們就不得不把當(dāng)前不可見狀態(tài)下的views清理掉,騰出更多的可使用內(nèi)存;當(dāng)前可見的view controller也要合理釋放掉一些緩存數(shù)據(jù),圖片資源和一些不是正在使用的資源,以避免應(yīng)用程序崩潰。
思路是這樣,具體的實施根據(jù)系統(tǒng)版本不同而略有差異,本文將詳細(xì)說明一下iOS 5與iOS 6的low-memory處理。
iOS 5 的處理
在iOS 6 之前,如果應(yīng)用程序接收到了low-memory警告,當(dāng)前不可見的view controllers會接收到viewDidUnload消息(也可以理解為自動調(diào)用viewDidUnload方法),所以我們需要在 viewDidUnload 方法中釋放掉所有 outlets ,以及可再次創(chuàng)建的資源。當(dāng)前可見的view controller 通過didReceiveMemoryWarning 合理釋放資源,具體見代碼注釋。
舉一個簡單的例子,有這樣一個view controller:
@interface MyViewController : UIViewController {
NSArray *dataArray;
}
@property (nonatomic, strong) IBOutlet UITableView *tableView;
@end
對應(yīng)的處理則為:
#pragma mark -
#pragma mark Memory management
- (void)didReceiveMemoryWarning {
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Relinquish ownership any cached data, images, etc that aren't in use.
}
- (void)viewDidUnload {
// Relinquish ownership of anything that can be recreated in viewDidLoad or on demand.
// For example: self.myOutlet = nil;
self.tableView = nil;
dataArray = nil;
[super viewDidUnload];
}
iOS 6 的處理
iOS 6 廢棄了viewDidUnload方法,這就意味著一切需要我們自己在didReceiveMemoryWarning中操作。
具體應(yīng)該怎么做呢?
1.將 outlets 置為 weak
當(dāng)view dealloc時,沒有人握著任何一個指向subviews的強(qiáng)引用,那么subviews實例變量將會自動置空。
@property (nonatomic, weak) IBOutlet UITableView *tableView;
2.在didReceiveMemoryWarning中將緩存數(shù)據(jù)置空
#pragma mark -
#pragma mark Memory management
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
dataArray = nil;
}
不要忘記一點,每當(dāng)tableview reload 的時候,需要判斷一下 dataArray ,若為空則重新創(chuàng)建。
兼容iOS 5 與 iOS 6
好吧,重點來了,倘若希望程序兼容iOS 5 與 iOS 6怎么辦呢? 這里有一個小技巧,我們需要對didReceiveMemoryWarning 做一些手腳:
#pragma mark -
#pragma mark Memory management
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
if ([self isViewLoaded] && self.view.window == nil) {
self.view = nil;
}
dataArray = nil;
}
判斷一下view是否是window的一部分,如果不是,那么可以放心的將self.view 置為空,以換取更多可用內(nèi)存。
這樣會是什么現(xiàn)象呢?假如,從view controller A 跳轉(zhuǎn)到 view controller B ,然后模擬low-memory警告,此時,view controller A 將會執(zhí)行self.view = nil ; 當(dāng)我們從 B 退回 A 時, A 會重新調(diào)用一次 viewDidLoad ,此時數(shù)據(jù)全部重新創(chuàng)建,簡單兼容無壓力~~
Note:
如果你好奇Apple為什么廢棄viewDidUnload,可以看看Apple 的解釋:
Apple deprecated viewDidUnload for a good reason. The memory savings from setting a few outlets to nil just weren’t worth it and added a lot of complexity for little benefit. For iOS 6+ apps, you can simply forget about view unloading and only implement didReceiveMemoryWarning if the view controller can let go of cached data that you can recreate on demand later.
如有失誤請各位路過大神即時指點,或有更好的做法,也請指點一二,在下感激不盡。