2012年9月27日 星期四

iOS 6裡被遺忘的viewDidUnload - 由來只見新人笑,有誰聽到舊人哭

在最新的iOS 6裡,有新加入,被大力宣傳和寵愛的新類別和新method。但由來只見新人笑,有誰聽到舊人哭。曾經在UIViewController佔舉足輕重地位的viewDidUnload method不再被呼叫了。過去當記憶體不足時,view controller的didReceiveMemoryWarning將被呼叫,接著為了節省記憶體空間,當其view被移除後,viewDidUnload將被呼叫。

接下來彼得潘實際建立一個簡單的Tab App Test來說明,感謝大畫家朋友張友鷦可愛圖片的友情提供。此App的第一個tab將顯示相愛的甜蜜氣氛,第二個tab則顯示親吻月亮的貓頭鷹。




為了驗證相關method的呼叫,我們將TestFirstViewController.m修改如下:


- (void)viewDidLoad
{
    NSLog(@"%s", __PRETTY_FUNCTION__);

    [super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}



-(void)viewDidUnload
{
    NSLog(@"%s", __PRETTY_FUNCTION__);

    [super viewDidUnload];
 
 
}

- (void)didReceiveMemoryWarning
{
    NSLog(@"%s", __PRETTY_FUNCTION__);

    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
 
}

除此之外,為了確定當記憶體不足時,不在畫面上的UI元件真的被狠心地移除,我們將在TestFirstViewController上的圖片類別設為自訂的MyImageView類別( 繼承UIImageView),並於MyImageView.m裡定義dealloc method。

-(void)dealloc
{
    NSLog(@"%s", __PRETTY_FUNCTION__);
}


好了,大功告成,好戲要上演了。先讓我們回到過去,看看iOS 5的表現吧。

iOS 5:

一開始先顯示第1個tab的畫面,此時viewDidLoad被呼叫,log如下。

2012-09-28 00:56:51.808 Test[10727:c07] -[TestFirstViewController viewDidLoad]

                                                  

切換到第2個tab,並從模擬器觸發記憶體不足情況。(Hardware -> Simulate Memory Warning)



此時log如下:

2012-09-28 00:57:57.826 Test[10727:c07] Received memory warning.
2012-09-28 00:57:57.828 Test[10727:c07] -[TestFirstViewController didReceiveMemoryWarning]
2012-09-28 00:57:57.828 Test[10727:c07] -[MyImageView dealloc]
2012-09-28 00:57:57.829 Test[10727:c07] -[TestFirstViewController viewDidUnload]

根據Log,我們觀察到當didReceiveMemoryWarning呼叫後,不在畫面上的第1個tab的view果然被狠心移除了,因為view上圖片被移除的log -[MyImageView dealloc]鐵證如山般印在log上。而當view整個被移除後,viewDidUnload果然跟著被呼叫了。

要是此時我們再回到第1個tab,由於此tab的view已被移除,它需要重新載入畫面,因此viewDidLoad再度被呼叫。

2012-09-28 01:05:32.063 Test[10727:c07] -[TestFirstViewController viewDidLoad]

但在iOS 6情況就完全不同了,讓我們趕緊接著看下去。

iOS 6:

照著跟剛剛一模一樣的步驟操作,卻發現印出的log精簡許多。

2012-09-28 01:07:35.275 Test[11146:c07] -[TestFirstViewController viewDidLoad]
2012-09-28 01:07:49.983 Test[11146:c07] Received memory warning.
2012-09-28 01:07:49.984 Test[11146:c07] -[TestFirstViewController didReceiveMemoryWarning]


當我們觸發記憶體不足時,didReceiveMemoryWarning如期地被呼叫,當接著呢? 什麼事都沒發生,第一個tab上的view沒被移除,viewDidUnload也不再被呼叫。這是因為在iOS 6系統給我們更高的自主權,讓我們自己決定記憶體不足時要做什麼。如果想要像從前一樣在記憶體不足時移除UI元件節省記憶體,也是可以,只要將TestFirstViewController的 didReceiveMemoryWarning修改如下。

- (void)didReceiveMemoryWarning
{
    NSLog(@"%s", __PRETTY_FUNCTION__);

    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
    
    if([self isViewLoaded] && self.view.window == nil)
    {
        NSLog(@"remove view");
        self.view = nil;
    }

}

說明:
利用isViewLoaded判斷view是否已經load,self.view.window判斷view是否目前不在畫面上,如果這兩個條件都成立,就可以放心大膽地移除view了。(將self.view設為nil)

再次執行:

2012-09-28 01:26:14.369 Test[11924:c07] -[TestFirstViewController viewDidLoad]
2012-09-28 01:26:19.026 Test[11924:c07] Received memory warning.
2012-09-28 01:26:19.028 Test[11924:c07] -[TestFirstViewController didReceiveMemoryWarning]
2012-09-28 01:26:19.029 Test[11924:c07] remove view
2012-09-28 01:26:19.029 Test[11924:c07] -[MyImageView dealloc]

觸發記憶體不足後,由於我們在didReceiveMemoryWarning裡將self.view設為nil,所以第1個tab上的view順利被殺掉了,其上的圖片也一併被解決了。

當我們再次切換回第一個tab時,viewDidLoad也像從前一樣,再一次被呼叫了。

2012-09-28 01:28:14.768 Test[11924:c07] -[TestFirstViewController viewDidLoad]


最後,最重要的,如果我們想要三天三夜不睡覺寫出的App可以同時支援iOS 5和iOS 6,要怎麼做呢?

很簡單,將didReceiveMemoryWarning method定義成剛剛的範例,即可同時支援iOS 5和iOS 6。因為在iOS 5時,觸發記憶體不足後,印出的Log如下:



2012-09-28 19:15:15.703 TestTab[40899:c07] -[TestTabFirstViewController viewDidLoad]
2012-09-28 19:15:20.817 TestTab[40899:c07] Received memory warning.
2012-09-28 19:15:20.818 TestTab[40899:c07] -[TestTabFirstViewController didReceiveMemoryWarning]
2012-09-28 19:15:20.826 TestTab[40899:c07] -[TestTabFirstViewController viewDidUnload]

由Log我們得知, didReceiveMemoryWarning裡if的判斷沒有成立。這是因為在iOS 5當呼叫[super didReceiveMemoryWarning]後,view即會被移除,接著viewDidUnload將被呼叫。等到viewDidUnload執行完後,才會繼續執行didReceiveMemoryWarning裡剩下的程式碼。因此當isViewLoaded被呼叫時,此時view已經被unload,所以isViewLoaded將回傳No。

除此之外,若是想在view被移除時做一些特定的工作,也請記得在iOS 5時要寫在viewDidUnload裡,在iOS 6則寫在didReceiveMemoryWarning裡的self.view = nil;之後。







1 則留言:

  1. http://thejoeconwayblog.wordpress.com/2012/10/04/view-controller-lifecycle-in-ios-6/

    看完上述的文章,我們原本以為正確的作法似乎又不正確了。

    回覆刪除