iphone下每個app可用的內(nèi)存是被限制的,如果一個app使用的內(nèi)存超過20M,則系統(tǒng)會向該app發(fā)送Memory Warning消息。收到此消息后,app必須正確處理,否則可能出錯或者出現(xiàn)內(nèi)存泄露。
app收到Memory Warning后會調(diào)用:UIapplication::didReceiveMemoryWarning -> UIApplicationDelegate::applicationDidReceiveMemoryWarning,然后調(diào)用當(dāng)前所有的viewController進(jìn)行處理。因此處理的主要工作是在viewController。
當(dāng)我們的程序在第一次收到內(nèi)存不足警告時,應(yīng)該釋放一些不用的資源,以節(jié)省部分內(nèi)存。否則,當(dāng)內(nèi)存不足情形依然存在,iOS再次向我們程序發(fā)出內(nèi)存不足的警告時,我們的程序?qū)籭OS kill掉。
iOS的UIViewController 類給我們提供了處理內(nèi)存不足的接口。在iOS 3.0 之前,當(dāng)系統(tǒng)的內(nèi)存不足時,UIViewController的didReceiveMemoryWarining 方法會被調(diào)用,我們可以在didReceiveMemoryWarining 方法里釋放掉部分暫時不用的資源。
從iOS3.0 開始,UIViewController增加了viewDidUnload方法。該方法和viewDIdLoad相配對。當(dāng)系統(tǒng)內(nèi)存不足時,首先UIViewController的didReceiveMemoryWarining 方法會被調(diào)用,而didReceiveMemoryWarining 會判斷當(dāng)前ViewController的view是否顯示在window上,如果沒有顯示在window上,則didReceiveMemoryWarining 會自動將viewcontroller 的view以及其所有子view全部銷毀,然后調(diào)用viewcontroller的viewdidunload方法。如果當(dāng)前UIViewController的view顯示在window上,則不銷毀該viewcontroller的view,當(dāng)然,viewDidunload也不會被調(diào)用了。但是到了ios6.0之后,這里又有所變化,ios6.0內(nèi)存警告的viewDidUnload 被屏蔽,即又回到了ios3.0的時期的內(nèi)存管理方式。
iOS3-iOS5.0以前版本收到內(nèi)存警告:
調(diào)用didReceiveMemoryWarning內(nèi)調(diào)用super的didReceiveMemoryWarning會將controller的view進(jìn)行釋放。所以我們不能將controller的view再次釋放。
處理方法:
java代碼
iOS6.0及以上版本的內(nèi)存警告:
調(diào)用didReceiveMemoryWarning內(nèi)調(diào)用super的didReceiveMemoryWarning調(diào)只是釋放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的宏開關(guān),保證是在6.0下使用的,6.0以前屏蔽以下代碼,否則會在下面使用self.view時自動加載viewDidUnLoad
if ([[UIDevice currentDevice].systemVersion floatValue] >= 6.0) {
//需要注意的是self.isViewLoaded是必不可少的,其他方式訪問視圖會導(dǎo)致它加載 ,在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;// 目的是再次進(jìn)入時能夠重新加載調(diào)用viewDidLoad函數(shù)。
}
}
}
但是似乎這么寫相對于以前并不省事。最終我們找到一篇文章,文章中說其實并不值得回收這部分的內(nèi)存,原因如下:
1. UIView是UIResponder的子類,而UIResponder有一個CALayer的成員變量,CALayer是具體用于將自己畫到屏幕上的。
2. CALayer是一個bitmap圖象的包裝類,當(dāng)UIView調(diào)用自身的drawRect時,CALayer才會創(chuàng)建這個bitmap圖象類。
3. 具體占內(nèi)存的其實是一個bitmap圖象類,CALayer只占48bytes, UIView只占96bytes。而一個iPad的全屏UIView的bitmap類會占到12M的大小!
4.在iOS6時,當(dāng)系統(tǒng)發(fā)出MemoryWarning時,系統(tǒng)會自動回收bitmap類。但是不回收UIView和CALayer類。這樣即回收了大部分內(nèi)存,又能在需要bitmap類時,根據(jù)CALayer類重建。
所以,iOS6這么做的意思是:我們根本沒有必要為了幾十byte而費力回收內(nèi)存。
--------------------------切糕分割線--------------
PS:
1、關(guān)于這個的官方文檔:https://developer.apple.com/library/ios/#featuredarticles/ViewControllerPGforiPhoneOS/ViewLoadingandUnloading/ViewLoadingandUnloading.html
2、zon2012貌似都沒有ios6的這個兼容(其實view是沒問題的,關(guān)鍵是資源)
分享到:
xcode4.5(iOS 6)開發(fā)與之前的差異 | IOS發(fā)送Email的方法
評論
1 樓 嘯笑天 2013-05-14
移動設(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的強引用,那么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.
新聞熱點
疑難解答