ViewController中玩過self.view = nil; 嗎?

在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.

如有失誤請各位路過大神即時指點,或有更好的做法,也請指點一二,在下感激不盡。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容