更多內容請挪步我的博客
[LEAF Photo] 中要處理圖片,如果操作時間長的話會收到 Memory Warning,內存處理的不好的話就會崩潰,下面記錄下處理內存警告的一些優化。
處理內存警告
處理所有 ViewController 中的 didReceiveMemoryWarning 方法
didReceiveMemoryWarning 方法中應當把緩存的變量都清空,這些變量最好都是通過懶加載的方式創建,這樣收到內存警告后也會自動加載。
@interface TestViewController ()
@property (strong, nonatomic) NSMutableArray *dataArray;
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
self.dataArray = nil;
}
- (NSMutableArray *)dataArray {
if (!_dataArray) {
_dataArray = [[NSMutableArray alloc] init];
}
// 賦值處理 ...
return _dataArray;
}
在處理圖片的 Controller 中當用戶點擊某個按鈕時會加載一些 View,例如處理文字、背景、繪圖筆等是彈出的設置頁面,這些 View 都封裝成了自定義的類從 Controller 中分離出去,這些類的內存可以被處理,如果在 didReceiveMemoryWarning 中把這些視圖清理時,需要判斷當前視圖是否是否正在使用,否則可能出現正在操作這些視圖時,由于收到內存警告而被刪除導致錯誤,在 iOS 6 以上的版本中可以用如下方式判斷視圖是否在使用
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
if ([self.view window] == nil) { // 視圖是否正在使用
self.settingView = nil;
}
}
另外,NSCache 緩存類會在收到 Memory Warning 時自動刪除緩存內容,不需要手動做清理,有個 Demo 點擊查看
還可以在 AppDelegate 中實現 applicationDidReceiveMemoryWarning 方法做一些全局數據清理
- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application {
}
消耗內存的對象創建放到 AutoReleasePool 中
函數中出現很多中間變量占據大量內存,或者不多的中間變量但是也是占用較大內存的,需要放到自動釋放池中
@autoreleasepool {
ALAsset *asset = self.assetArray[i];
if (asset && ![asset isEqual:[NSNull null]]) {
UIImage *img = [UIImage imageWithCGImage:[[asset defaultRepresentation] fullScreenImage]];
// ...
}
}
使用 Allocations 工具查看是否有應該釋放但是沒有被釋放的內存
如果進入一個頁面后退出該頁面,內存沒有降回或者接近降回到進入該頁面前的內存數,說明該頁面有內存沒有被釋放。
例如點擊 [PhotoBook] 中點擊添加照片的按鈕,添加照片后退出這個頁面后發現內存中駐留了該頁面的內存,而該段內存是存在 LTPhotoPickerViewController 的某個變量中的,查看頁面代碼發現添加圖片按鈕中有如下代碼,每次點擊都創建了相同的控制器,但是沒有對其釋放的地方,所以創建了多個重復的對象,而這個對象中保存了很多內存。
- (void)stickerPhotoAction:(id)sender {
LTPhotoPickerViewController *pickerController = [[LTPhotoPickerViewController alloc] init];
[self.navigationController pushViewController:pickerController animated:YES];
}
這種簡單的錯誤很容易修改,只是早期的時候腦袋壞掉不小心寫錯,要解決問題最困難的問題不是如何修改,而是如何找到問題在哪。
修改方法:將 pickerController 改為屬性,每次點擊按鈕的時候使用同樣的屬性對象即可。
循環引用
除了 Block 內部要注意不要引用自己,還要注意是否有屬性應當是 weak 的,但是設置成 strong,導致內部引用計數錯誤導致的無法釋放,這種現象也要靠 Allocations 工具檢查,頁面退出后還有對象被 Persistent,可以看看是否有該 weak 的被 strong 了。
如果使用照片盡量對其進行壓縮節省內存
// ALAsset *asset = ...;
UIImage *img = [UIImage imageWithCGImage:[[asset defaultRepresentation] fullScreenImage]];
NSData *imgData = UIImageJPEGRepresentation(fullScreenImage, 0.7);
UIImage *img = [UIImage imageWithData:imgData];
// scaled
UIImage *img = [UIImage imageWithCGImage:[[asset defaultRepresentation] fullScreenImage] scale:scale orientation:UIImageOrientationUp];
同樣是獲取相冊中的照片,兩種方式進行壓縮節省內存
圖片保存時注意保存圖片的大小
如果需要自己創建圖片上下文并繪制,給用戶選擇的圖片大小最大不要超過 4096 ** 4096 像素,可以給用戶大、中、小幾種選擇。
創建圖片上下文使用 UIGraphicsBeginImageContextWithOptions(size, YES, [UIScreen mainScreen].scale); 的話,在主流機上 scale 都是 2,那么創建出的圖片像素都是 size 大小的 2倍,如果讓用戶選擇 3200 ** 3200 大小的照片,保存后就是 6400 ** 6400,圖片這么大很容易造成內存吃緊,收到警告,所以不要使用 UIGraphicsBeginImageContextWithOptions 方式,保存圖片后看下圖片是否是想要的大小。
GCD Timer 的使用注意事項
NSTimer 一定要在恰當的地方執行 invalidate,否則會造成內存泄漏,但是用 GCD Timer 就可以繞過這個問題。但是在 [PhotoBook] 項目中添加 GCD Timer 后想要在某種情況下,把 Timer 暫停,于是設置了某個變量,當該變量在某種情況下將 timer 暫停,于是有了下面的代碼
dispatch_source_set_event_handler(timer, ^() {
// if (condition) {
// dispatch_suspend(timer);
// }
}
dispatch_resume(timer);
發現在 dispatch_source_set_event_handler 中加上 dispatch_suspend,該頁面無法被釋放。具體分析請挪步這里