1. 程式人生 > >IOS 記憶體警告 Memory warning level

IOS 記憶體警告 Memory warning level

我們都知道在移動裝置上很多資源都是比較緊缺的,尤其時記憶體,通常都比較小,iPhone4也才只有512MB。而且IOS4.0以後還支援了多工,這個問題就更加突出了。因此我們在平時設計程式的時候要注意管理好記憶體,減少不必要的開銷,謹防洩露。

  由於寫的一個小專案存在嚴重的記憶體洩漏,程式經常執行時間不長就退出了,除錯時候發現執行過程中接受到系統的Memry warning level 1幾次以後,緊接著收到一個Memory warning level 2,然後程式退出。通常使用xcode4自帶的工具,跟蹤發現由於一個影象資源忘記release導致。問題解決了,但我沒有打算就此帶過。接著上網查找了有關Memory warning Level的資料,後來在stackoverflow上面找到一些有用的資料,這是

KennyTM寫的,跟大家分享一下:

    系統有四種記憶體警告,定義如下:

    typedef enum {

        OSMemoryNotificationLevelAny      = -1,

        OSMemoryNotificationLevelNormal   =  0,

        OSMemoryNotificationLevelWarning  =  1,

        OSMemoryNotificationLevelUrgent   =  2,

        OSMemoryNotificationLevelCritical =  3

    } OSMemoryNotificationLevel;

  但是這幾種警告之間是怎麼變化的,到達什麼閾值時會從一個level跳到另外一個level,文章中沒有提及。

  通常我們在程式中接收到最多的就是Memory warning level 1,這個時候就證明系統記憶體緊張了,我們的程式必須做出相應,釋放不必要的記憶體。通常如果我們處理得當的話,記憶體警告就會到此停止,恢復正常狀態。如果我們在接收到memory warning level 1以後坐視不理的話,系統可能還會再嘗試通知幾次level 1,如果還是不去處理的話,系統將會發出更高一級的記憶體警告 level 2,通常的結果就是我們的App被強制退出,系統收回記憶體。當然系統在發出level 2的警告時,也會取嘗試做一些清理記憶體的事,例如關閉不必要的後臺程式等。很顯然我們不該寄希望於系統自己清理,這就好比你的胳膊被劃破了,身體肯定會有自修復功能,但是如果傷口比較大的話,肯定還是需要人工處理一下的。

  到目前位置我還沒有見過level 3的警告,根據stack over flow上面講的“當發生level 3警告時,系統核心將會接管,很有可能關掉IOS的主介面程序(SpringBorad),甚至會重啟”,照這個意思來說我們程式裡面接收不到,也實屬正常,系統自己的東西都被幹掉了,我們肯定早被kill了。

  如果開啟上面的連線,可以看到定義OSMemoryNotificationLevel的原始檔,你可能會注意到裡面有一個函式OSMemoryNotificationCurrentLevel()可以用來獲取當前記憶體告警level,不過需要加標頭檔案#import <libkern/OSMemoryNotification.h>,網上有個檢視當前記憶體資料的開原始碼MemWatcher,大家可以看看,說實話我沒有看懂。

  說了這麼多,希望能幫大家弄清楚記憶體警告是怎麼回事兒,通常我們程式是不會碰到記憶體警告,平時寫程式碼注意在所有alloc,retain,copy的地方,對應的寫上release,或者直接建立autorelease物件(個人不推薦這麼做),釋出前養成檢查記憶體洩露的好習慣。

  順便提一下如果執行過程中遇到記憶體警告的話,程式通常情況下都先呼叫AppDelegate中的applicationDidReceiveMemoryWarning, 然後程式會通知各ViewController,呼叫其didRecieveMemoryWarning方法,這個時候我們一定要種,釋放不必要的資源。

  今天回家寫了個小例子測試記憶體洩漏,思路如下:在UIViewController的viewDidload中,向self.view新增一個自定義的專門洩漏的UIView(在初始化函式中開啟一個執行緒不停的家在圖片)。在我的iPhone4下執行程式後大概不到一分鐘以後出現level 1 警告,然後過10秒左右報出level 2警告,再過5秒左右程式被退出。截圖如下:

  這也證明前面說過的level 3的警告通常我們是接收不到的這個推斷。

SvMemoryWarningViewController.m

//
//  SvMemoryWarningViewController.m
//  SvMemoryWarning
//
//  Created by maple on 3/9/12.
//  Copyright (c) 2012 SmileEvday. All rights reserved.
//

#import "SvMemoryWarningViewController.h"
#import <libkern/OSMemoryNotification.h>
#import "SvLeakMemoryView.h"

@interface SvMemoryWarningViewController ()

@end

@implementation SvMemoryWarningViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    SvLeakMemoryView *view = [[SvLeakMemoryView alloc] initWithFrame:CGRectMake(0, 0, 320, 480)];
    [self.view addSubview:view];
    [view release];
}

- (void)viewDidUnload
{
    [super viewDidUnload];
    // Release any retained subviews of the main view.
}

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
    return interfaceOrientation == UIInterfaceOrientationPortrait;
}

- (void)didReceiveMemoryWarning
{
    
    NSLog(@"Recieve memory warning");
    NSLog(@"~~~~~~~~~~~~~~level~~~~~~~~~~~~~~~ %d", (int)OSMemoryNotificationCurrentLevel());
}

@end
SvLeakMemoryView.m

#import "SvLeakMemoryView.h"
#import <QuartzCore/QuartzCore.h>

@implementation SvLeakMemoryView

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        // Initialization code
        
        UIImageView *imageView = [[UIImageView alloc] initWithFrame:frame];
        
        NSString *filePath = [[NSBundle mainBundle] pathForResource:@"Car" ofType:@"jpg"];
        imageView.image = [[UIImage alloc] initWithContentsOfFile:filePath];
        
        imageView.layer.contentsGravity = kCAGravityResizeAspect;
        [self addSubview:imageView];
        
        NSString *filePath2 = [[NSBundle mainBundle] pathForResource:@"treeHighResolution" ofType:@"jpg"]; 
        dispatch_queue_t loadImageQueue = dispatch_queue_create("load image", NULL);
        
        __block UIImageView *testIV = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 320, 480)];
        dispatch_async(loadImageQueue, ^{
            while (TRUE) {
                UIImage *image = [[UIImage alloc] initWithContentsOfFile:filePath2];
                testIV.image = image;
            }
        });
    }
    return self;
}

/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
    // Drawing code
}
*/

@end