[ios]received memory warning

來源:互聯網
上載者:User

標籤:

參考:http://blog.sina.com.cn/s/blog_68661bd80101nn6p.html

 

IPhone下每個app可用的記憶體是被限制的,如果一個app使用的記憶體超過20M,則系統會向該app發送Memory Warning訊息。蘋果公司系統工程師建議,應用程式所佔記憶體不應該超過20MB,開發人員圈內流傳著一個粗略的經驗法則:當應用程式佔用了大約20MB記憶體時,iphone開始發出記憶體警告。當應用程式所佔記憶體大約為30MB時,iphone OS會關閉應用程式。收到此訊息後,app必須正確處理,否則可能出錯或者出現記憶體泄露。app收到Memory Warning後會調用:UIApplication::didReceiveMemoryWarning -> UIApplicationDelegate::applicationDidReceiveMemoryWarning,然後調用當前所有的 viewController進行處理。因此處理的主要工作是在viewController。

我們知道,建立viewcontroller時,執行順序是loadview -> viewDidLoad。

當收到記憶體警告時,如果viewcontroller未顯示(在後台),會執行didReceiveMemoryWarning -> viewDidUnLoad;如果viewcontroller當前正在顯示(在前台),則只執行didReceiveMemoryWarning。

當重新顯示該viewController時,執行過viewDidUnLoad的viewcontroller(即原來在後台)會重新調用loadview -> viewDidLoad。

重載didReceiveMemoryWarning時,一定調用這個函數的super實現來允許父類(一般是UIVIewController)釋放self.view。self.view釋放之後,會調用下面的viewDidUnload函數.也就是說,儘管self.view是被處理了,但是outlets的變數因為被retain過,所以不會被釋放,為瞭解決這個問題,就需要在viewDidUnload中釋放這些retain過的outlets變數。通常controller會儲存nib檔案建立的views的引用,但是也可能會儲存著loadView函數建立的對象的引用。最完美的方法是使用合成器方法:

self.myCertainView = nil;
這樣合成器會release這個view,如果你沒有使用property,那麼你得自己顯式釋放這個view。

因此主要注意下面幾個函數:

loadView 建立view,構建介面;
viewDidLoad 做些初始化工作。由於在初次建立viewcontroller和重新恢複時都會調用,因此這個函數需要注意區分不同的情況,設定正確的狀態。
didReceiveMemoryWarning 釋放不必須的記憶體,比如緩衝,未顯示的view等。
viewDidUnLoad 最大程度的釋放可以釋放的記憶體。比如應該釋放view,這些view在調用loadview後可以重建。(其中成員變數釋放後應設定為nil)。對於非介面的資料是否釋放,需要具體分析,可以恢複的資料可以釋放,不能恢複的資料就不要釋放。

實際中如果viewcontroller是用xib產生的介面,則需要我們做的就比較少,主要是在viewDidLoad中恢複原來的介面狀態。

如果是通過編程建立的介面,則需要做的工作就要更多些,上面4個函數中都需要進行正確處理。

iOS6.0及其以後,viewDidUnload不再有用,收到low-memeory時系統不會釋放Views。
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時自動載入viewDidLoad
    if ([self.view window] == nil)// 是否是正在使用的視圖
       
    {
        // 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而費力回收記憶體。
行動裝置終端的記憶體極為有限,應用程式必須做好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 做一些手腳:
#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 置為空白,以換取更多可用記憶體。

這 樣會是什麼現象呢?假如,從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.


原文地址:http://justsee.iteye.com/blog/1820588
官方文檔:https://developer.apple.com/library/ios/#featuredarticles/ViewControllerPGforiPhoneOS/ViewLoadingandUnloading/ViewLoadingandUnloading.html

ViewController的生命週期和didReceiveMemoryWarning後的流程:http://blog.csdn.net/iunion/article/details/8699491


ViewController的生命週期中各方法執行流程如下: init—>loadView—>viewDidLoad—>viewWillApper—>viewDidApper—>viewWillDisapper—>viewDidDisapper—>viewWillUnload->viewDidUnload—>dealloc
跟隨如下文字理解viewController對view載入過程:

1 先判斷子類是否重寫了loadView,如果有直接調用。之後調viewDidLoad完成View的載入。

2 如果是外部通過調用initWithNibName:bundle指定nib檔案名稱的話,ViewController記載此nib來建立View。

3 如果initWithNibName:bundle的name參數為nil,則ViewController會通過以下兩個步驟找到與其關聯的nib。

A 如果類名包含Controller,例如ViewController的類名是MyViewController,則尋找是否存在MyView.nib;

B 找跟ViewController類名一樣的檔案,例如MyViewController,則尋找是否存在MyViewController.nib。

4 如果子類沒有重寫的loadView,則ViewController會從StroyBoards中找或者調用其預設的loadView,預設的loadView返回一個空白的UIView對象。

注意第一步,ViewController是判斷子類是否重寫了loadView,而不是判斷調用子類的loadView之後ViewController的View是否為空白。就是說,如果子類重寫了loadView的話,不管子類在loadView裡面能否擷取到View,ViewController都會直接調viewDidLoad完成View的載入

 

那為什麼要寫成 self.myOutlet = nil; ,實際上這個文法是執行了 property 裡的setter 方法,而不是一個簡單的變數賦值,它幹了兩件事:1、老資料 release 掉,2、新資料(nil)retain(當 property 設定為 retain 的情況下),當然對 nil retain 是無意義的。如果寫成 myOutlet = nil,那就是簡單的把 myOutlet 指向 nil,這樣記憶體就泄漏了,因為老資料沒有 release。而如果僅僅寫成 [myOutlet release] 也會有問題,因為當 view 被 dealloc 的時候會 再次 release,程式就出錯了,而對 nil release 是沒有問題的。

dealloc 是當前 viewController 被釋放的時候,清空所有當前 viewController 裡面的實體和資料來釋放記憶體,該方法也是自動調用的,無需手動執行。舉例說明當 modalView 被 dismissModalViewControllerAnimated 或者 navigationController 回到上一頁的時候,這個方法就會被自動調用。因為這個頁面已經不再使用了,所以可以把所有實體和資料都釋放(release)掉。

[ios]received memory warning

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.