天天看點

UIViewController的生命周期及iOS程式執行順序 和ios6 處理記憶體警告

當一個視圖控制器被建立,并在螢幕上顯示的時候。 代碼的執行順序

1、 alloc                                   建立對象,配置設定空間

2、init (initWithNibName) 初始化對象,初始化資料

3、loadView                          從nib載入視圖 ,通常這一步不需要去幹涉。除非你沒有使用xib檔案建立視圖

4、viewDidLoad                   載入完成,可以進行自定義資料以及動态建立其他控件

5、viewWillAppear              視圖将出現在螢幕之前,馬上這個視圖就會被展現在螢幕上了

6、viewDidAppear               視圖已在螢幕上渲染完成

當一個視圖被移除螢幕并且銷毀的時候的執行順序,這個順序差不多和上面的相反

1、viewWillDisappear            視圖将被從螢幕上移除之前執行

2、viewDidDisappear             視圖已經被從螢幕上移除,使用者看不到這個視圖了

3、dealloc                                 視圖被銷毀,此處需要對你在init和viewDidLoad中建立的對象進行釋放

關于viewDidUnload :在發生記憶體警告的時候如果本視圖不是目前螢幕上正在顯示的視圖的話, viewDidUnload将會被執行,本視圖的所有子視圖将被銷毀,以釋放記憶體,此時開發者需要手動對viewLoad、viewDidLoad中建立的對象釋放記憶體。 因為當這個視圖再次顯示在螢幕上的時候,viewLoad、viewDidLoad 再次被調用,以便再次構造視圖。

當我們建立一個UIViewController類的對象時,通常系統會生成幾個預設的方法,這些方法大多與視圖的調用有關,但是在視圖調用時,這些方法的調用順序如何,需要整理下。

通常上述方法包括如下幾種,這些方法都是UIViewController類的方法:

- (void)viewDidLoad;

- (void)viewDidUnload;

- (void)viewWillAppear:(BOOL)animated;

- (void)viewDidAppear:(BOOL)animated;

- (void)viewWillDisappear:(BOOL)animated;

- (void)viewDidDisappear:(BOOL)animated;

下面介紹下APP在運作時的調用順序。

1)- (void)viewDidLoad;

      一個APP在載入時會先通過調用loadView方法或者載入IB中建立的初始界面的方法,将視圖載入到記憶體中。然後會調用viewDidLoad方法來進行進一步的設定。通常,我們對于各種初始資料的載入,初始設定等很多内容,都會在這個方法中實作,是以這個方法是一個很常用,很重要的方法。

      但是要注意,這個方法隻會在APP剛開始加載的時候調用一次,以後都不會再調用它了,是以隻能用來做初始設定。

2) - (void)viewDidUnload;

      在記憶體足夠的情況下,軟體的視圖通常會一直儲存在記憶體中,但是如果記憶體不夠,一些沒有正在顯示的viewcontroller就會收到記憶體不夠的警告,然後就會釋放自己擁有的視圖,以達到釋放記憶體的目的。但是系統隻會釋放記憶體,并不會釋放對象的所有權,是以通常我們需要在這裡将不需要在記憶體中保留的對象釋放所有權,也就是将其指針置為nil。

      這個方法通常并不會在視圖變換的時候被調用,而隻會在系統退出或者收到記憶體警告的時候才會被調用。但是由于我們需要保證在收到記憶體警告的時候能夠對其作出反應,是以這個方法通常我們都需要去實作。

      另外,即使在裝置上按了Home鍵之後,系統也不一定會調用這個方法,因為IOS4之後,系統允許将APP在背景挂起,并将其繼續滞留在記憶體中,是以,viewcontroller并不會調用這個方法來清除記憶體。

3)- (void)viewWillAppear:(BOOL)animated;

      系統在載入所有資料後,将會在螢幕上顯示視圖,這時會先調用這個方法。通常我們會利用這個方法,對即将顯示的視圖做進一步的設定。例如,我們可以利用這個方法來設定裝置不同方向時該如何顯示。

      另外一方面,當APP有多個視圖時,在視圖間切換時,并不會再次載入viewDidLoad方法,是以如果在調入視圖時,需要對資料做更新,就隻能在這個方法内實作了。是以這個方法也非常常用。

4) - (void)viewDidAppear:(BOOL)animated;

      有時候,由于一些特殊的原因,我們不能在viewWillApper方法裡,對視圖進行更新。那麼可以重寫這個方法,在這裡對正在顯示的視圖進行進一步的設定。

5) - (void)viewWillDisappear:(BOOL)animated;

      在視圖變換時,目前視圖在即将被移除、或者被覆寫時,會調用這個方法進行一些善後的處理和設定。

      由于在IOS4之後,系統允許将APP在背景挂起,是以在按了Home鍵之後,系統并不會調用這個方法,因為就這個APP本身而言,APP顯示的view,仍是挂起時候的view,是以并不會調用這個方法。

6) - (void)viewDidDisappear:(BOOL)animated;

      我們可以重寫這個方法,對已經消失,或者被覆寫,或者已經隐藏了的視圖做一些其他操作。

OS 開發init和 loadView 和 viewDidLoad 的差別

 他們都可以用來在視圖載入的時候,初始化一些内容。 但是他們有什麼差別呢?

1.loadview裡面必須為控制器的view指派,要麼繼承要麼執行個體化一個;主要作用是為了執行個體化view

2.執行順序,init->load->viewDidLoad

3.如果裝置記憶體不足的時候, view 控制器會收到didReceiveMemoryWarning的消息。預設的實作是檢查目前控制器的view是否在使用。如果它的view不在目前正在使用的view hierarchy裡面,且你的控制器實作了loadView方法,那麼這個view将被release, loadView方法将被再次調用來建立一個新的view。是以不要再init中寫任何和view相關的内容.

func application(application: UIApplication!, didFinishLaunchingWithOptions launchOptions: NSDictionary!) -> Bool {
        // Override point for customization after application launch.
        
        self.window =  UIWindow(frame: UIScreen.mainScreen().bounds);
        self.window!.backgroundColor = UIColor.whiteColor();
        self.window!.makeKeyAndVisible();
        
        var rootController = ViewController(nibName: nil,bundle: nil);
        self.window!.rootViewController = rootController;
        return true
    }      
class ViewController: UIViewController {
    
    var testView1:TestView?;
    var testView2:TestView?;

    override func loadView(){
        self.view = UIView();  //可自定義或者執行self.loadView();確定self.view被執行個體化
    }
    
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        var label = UILabel(frame: CGRect(x:0,y:UIScreen.mainScreen().bounds.height/2+50,width:320,height:44));
        label.text = "aaaaa";
 }      

iPhone下每個app可用的記憶體是被限制的,如果一個app使用的記憶體超過20M,則系統會向該app發送Memory Warning消息。收到此消息後,app必須正确處理,否則可能出錯或者出現記憶體洩露。

     app收到Memory Warning後會調用:UIApplication::didReceiveMemoryWarning -> UIApplicationDelegate::applicationDidReceiveMemoryWarning,然後調用目前所有的viewController進行處理。是以處理的主要工作是在viewController。

    當我們的程式在第一次收到記憶體不足警告時,應該釋放一些不用的資源,以節省部分記憶體。否則,當記憶體不足情形依然存在,iOS再次向我們程式發出記憶體不足的警告時,我們的程式将會被iOS kill掉。

    iOS的UIViewController 類給我們提供了處理記憶體不足的接口。在iOS 3.0 之前,當系統的記憶體不足時,UIViewController的didReceiveMemoryWarining 方法會被調用,我們可以在didReceiveMemoryWarining 方法裡釋放掉部分暫時不用的資源。

    從iOS3.0 開始,UIViewController增加了viewDidUnload方法。該方法和viewDIdLoad相配對。當系統記憶體不足時,首先UIViewController的didReceiveMemoryWarining 方法會被調用,而didReceiveMemoryWarining 會判斷目前ViewController的view是否顯示在window上,如果沒有顯示在window上,則didReceiveMemoryWarining 會自動将viewcontroller 的view以及其所有子view全部銷毀,然後調用viewcontroller的viewdidunload方法。如果目前UIViewController的view顯示在window上,則不銷毀該viewcontroller的view,當然,viewDidunload也不會被調用了。但是到了ios6.0之後,這裡又有所變化,ios6.0記憶體警告的viewDidUnload 被屏蔽,即又回到了ios3.0的時期的記憶體管理方式。   

    iOS3-iOS5.0以前版本收到記憶體警告:

調用didReceiveMemoryWarning内調用super的didReceiveMemoryWarning會将controller的view進行釋放。是以我們不能将controller的view再次釋放。

處理方法:

Java代碼  

  1. -(void)didReceiveMemoryWarning  
  2.        {  
  3.                 [super didReceiveMemoryWarning];//如沒有顯示在window上,會自動将self.view釋放。  
  4.                 // ios6.0以前,不用在此做處理,self.view釋放之後,會調用下面的viewDidUnload函數,在viewDidUnload函數中做處理就可以了。  
  5.        }  
  6.        -(void)viewDidUnload  
  7.               // Release any retained subviews of the main view.不包含self.view  
  8.               //處理一些記憶體和資源問題。  
  9.                [super viewDidUnload];  

    iOS6.0及以上版本的記憶體警告:

調用didReceiveMemoryWarning内調用super的didReceiveMemoryWarning調隻是釋放controller的resouse,不會釋放view

    -(void)didReceiveMemoryWarning

    {

            [super didReceiveMemoryWarning];//即使沒有顯示在window上,也不會自動的将self.view釋放。

            // Add code to clean up any of your own resources that are no longer necessary.

            // 此處做相容處理需要加上ios6.0的宏開關,保證是在6.0下使用的,6.0以前屏蔽以下代碼,否則會在下面使用self.view時自動加載viewDidUnLoad

            if ([[UIDevice currentDevice].systemVersion floatValue] >= 6.0) {

             //需要注意的是self.isViewLoaded是必不可少的,其他方式通路視圖會導緻它加載 ,在WWDC視訊也忽視這一點。

             if (self.isViewLoaded && !self.view.window)// 是否是正在使用的視圖

             {

                   // Add code to preserve data stored in the views that might be

                   // needed later.

                   // Add code to clean up other strong references to the view in

                   // the view hierarchy.

                   self.view = nil;// 目的是再次進入時能夠重新加載調用viewDidLoad函數。

             }

           }

    }

但是似乎這麼寫相對于以前并不省事。最終我們找到一篇文章,文章中說其實并不值得回收這部分的記憶體,原因如下:

1. UIView是UIResponder的子類,而UIResponder有一個CALayer的成員變量,CALayer是具體用于将自己畫到螢幕上的。

2. CALayer是一個bitmap圖象的包裝類,當UIView調用自身的drawRect時,CALayer才會建立這個bitmap圖象類。

3. 具體占記憶體的其實是一個bitmap圖象類,CALayer隻占48bytes, UIView隻占96bytes。而一個iPad的全屏UIView的bitmap類會占到12M的大小!

4.在iOS6時,當系統發出MemoryWarning時,系統會自動回收bitmap類。但是不回收UIView和CALayer類。這樣即回收了大部分記憶體,又能在需要bitmap類時,根據CALayer類重建。

    是以,iOS6這麼做的意思是:我們根本沒有必要為了幾十byte而費力回收記憶體。

--------------------------切糕分割線--------------

PS:

1、關于這個的官方文檔:https://developer.apple.com/library/ios/#featuredarticles/ViewControllerPGforiPhoneOS/ViewLoadingandUnloading/ViewLoadingandUnloading.html

2、zon2012貌似都沒有ios6的這個相容(其實view是沒問題的,關鍵是資源)

移動裝置終端的記憶體極為有限,應用程式必須做好low-memory處理工作,才能避免程式因記憶體使用過大而崩潰。

low-memory 處理思路

通常一個應用程式會包含多個view controllers,當從view跳轉到另一個view時,之前的view隻是不可見狀态,并不會立即被清理掉,而是儲存在記憶體中,以便下一次的快速顯現。但是如果應用程式接收到系統發出的low-memory warning,我們就不得不把目前不可見狀态下的views清理掉,騰出更多的可使用記憶體;目前可見的view controller也要合理釋放掉一些緩存資料,圖檔資源和一些不是正在使用的資源,以避免應用程式崩潰。

思路是這樣,具體的實施根據系統版本不同而略有差異,本文将詳細說明一下iOS 5與iOS 6的low-memory處理。

iOS 5 的處理

在iOS 6 之前,如果應用程式接收到了low-memory警告,目前不可見的view controllers會接收到viewDidUnload消息(也可以了解為自動調用viewDidUnload方法),是以我們需要在 viewDidUnload 方法中釋放掉所有 outlets ,以及可再次建立的資源。目前可見的view controller 通過didReceiveMemoryWarning 合理釋放資源,具體見代碼注釋。

舉一個簡單的例子,有這樣一個view controller:

@interface MyViewController : UIViewController {  

    NSArray *dataArray;  

}  

@property (nonatomic, strong) IBOutlet UITableView *tableView;  

@end 

對應的處理則為:

#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中操作。

具體應該怎麼做呢?

1.将 outlets 置為 weak

當view dealloc時,沒有人握着任何一個指向subviews的強引用,那麼subviews執行個體變量将會自動置空。

@property (nonatomic, weak) IBOutlet UITableView *tableView;

2.在didReceiveMemoryWarning中将緩存資料置空

#pragma mark -   

#pragma mark Memory management   

- (void)didReceiveMemoryWarning  

{  

    [super didReceiveMemoryWarning];  

    // Dispose of any resources that can be recreated.   

    dataArray = nil;  

不要忘記一點,每當tableview reload 的時候,需要判斷一下 dataArray ,若為空則重新建立。

相容iOS 5 與 iOS 6

好吧,重點來了,倘若希望程式相容iOS 5 與 iOS 6怎麼辦呢? 這裡有一個小技巧,我們需要對didReceiveMemoryWarning 做一些手腳:

- (void)didReceiveMemoryWarning

{

    if ([self isViewLoaded] && self.view.window == nil) {

        self.view = nil;

    }

判斷一下view是否是window的一部分,如果不是,那麼可以放心的将self.view 置為空,以換取更多可用記憶體。

這樣會是什麼現象呢?假如,從view controller A 跳轉到 view controller B ,然後模拟low-memory警告,此時,view controller A 将會執行self.view = nil ; 當我們從 B 退回 A 時, A 會重新調用一次 viewDidLoad ,此時資料全部重新建立,簡單相容無壓力~~

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.