内存不足时的iOS应用程序行为 [英] iOS application behaviour in case of low memory

查看:380
本文介绍了内存不足时的iOS应用程序行为的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有iPhone应用程序。在某些情况下,当设备的可用内存不足时,某些操作(例如,打开相机)可能会导致应用程序崩溃。



我的问题是:


  1. 我想防止这些崩溃,应用程序
    执行此类操作的常见方法是什么(阻止特定操作,通知用户,其他
    想法)?我问,因为我在遇到的iOS
    应用程序中没有遇到过这样的行为。

  2. 有没有办法防止此类崩溃并保持完整的应用程序功能,例如iOS系统要求释放更多内存等?如果有人有最佳实践或良好的启发式,我很乐意听到它。

编辑:我问这个问题假设我已经实现了'didReceiveMemoryWarning'功能并释放了我可以的所有内存。



编辑2:我的应用程序是关于图片。很像相机扫描仪应用程序,这个应用程序允许拍照,图像处理和在内存中保存有关它们的数据。当我扫描很多照片时,我的崩溃通常会发生。

解决方案

我遵循的一些拇指规则:


  1. 使用Arc


  2. 对iboutlet使用weak(顶级示例除外:UIwindow)和代表


  3. 对类属性使用Strong,对NSString使用copy。


  4. 不要直接访问变量,请使用self ....方式。


  5. 不要使用autorelease方式创建新对象,例如NSArray * array = [NSArray arrayWithObjects .......,而不是使用NSArray * array = [ NSArray alloc] initWit ....



    NSString类的方法相同。尝试使用[NSString alloc] initWithFormat .....而不是[NSString stringWithFormat。


  6. 当你添加NSNotification(addObserver ...)时必须在dealloc中删除(removeObserver ..)它们。


  7. 实现didReceiveMemoryWarning(查看控制器级别)或applicationDidReceiveMemoryWarning(应用程序级别,它首先被调用,而不是视图控制器级别)恰当的,有时候你只有你想要从崩溃中保存。你可以显示警告告诉用户可用的内存较少,你可以弹出/提供..user到主屏幕。(不好的做法)。


  8. 在后台线程中不要对主线程执行任何操作。总是使用@autorelease块作为后台线程。


  9. 对长时间运行的进程使用GCD / NSOperation队列。


  10. 密切关注您正在使用的图像资源,仅使用所需尺寸的图像,而不是根据需要将大图像缩放到小图像尺寸。


  11. USE自动释放池用于长时间运行的循环,它会创建大量自动释放的对象。


我有一些ypu可以遵循的代码片段:

  //方式1全部开启主线程坏方法,基本上我们只是在主线程上做一些图像处理(不应该在主线程上:) :) 
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {

YourApplicationDelegate * appDelegate =(YourApplicationDelegate *)[[UIApplication sharedApplication] delegate];
[appDelegate showLandscapeLoading]; //认为它是进度视图/加载器

UIImage * pickedImage = [info objectForKey:UIImagePickerControllerOriginalImage];
NSData * imageData = UIImagePNGRepresentation(pickedImage);

NSString * documentsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES)lastObject];
NSString * path = [documentsDirectory stringByAppendingPathComponent:@category_imagename.jpeg];

NSError * error = nil;
//来自这里
[imageData writeToFile:路径选项:NSDataWritingAtomic错误:& error];

** //讨论重要部分主要线程上的UI操作坏坏坏**
CGSize size1; // A
size1.width = 400;
size1.height = 400;
UIGraphicsBeginImageContext(size1);
[pickedImage drawInRect:CGRectMake(0,0,size1.width,size1.height)];
UIImage * bigImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

NSString * bigThumb = [documentsDirectory stringByAppendingPathComponent:@category_thumb_imagename.jpeg];

NSData * data1 = UIImageJPEGRepresentation(bigImage,0.5);
BOOL status1 = [data1 writeToFile:bigThumb atomically:YES];
** //到这里应该是非ui线程/单独的线程**
** //下面的代码应该进入主线程**
NSLog(@status1 - > ;%d,status1);
[self setCategoryImageName:bigImage];
[self.imgCategory setImage:pickedImage];

if(status1){
isAddingCategoryImage = YES;
}

[appDelegate stopLandscapeLoading];

if(error!= nil){
NSLog(@Error:%@,error);
返回;
}

if([self.popoverController isPopoverVisible]){
[self.popoverController dismissPopoverAnimated:true];

}

[picker.view removeFromSuperview];

}

正确方法:



使用NSOperation:

   - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary * )info {

YourApplicationDelegate * appDelegate =(YourApplicationDelegate *)[[UIApplication sharedApplication] delegate];
[appDelegate showLandscapeLoading];

UIImage * pickedImage = [info objectForKey:UIImagePickerControllerOriginalImage];

NSString * documentsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES)lastObject];

NSError * error = nil;



NSOperationQueue * opQueue = [[NSOperationQueue alloc] init];
[opQueue addOperationWithBlock:^
{
//创建图形图像上下文非常慢的东西
CGSize newSize = CGSizeMake(400,400);
UIGraphicsBeginImageContext(newSize);
//告诉旧图像在这个新的上下文中绘制,使用所需的
//新大小
[pickedImage drawInRect:CGRectMake(0,0,newSize.width,newSize.height) ]。
//从上下文中获取新图像
UIImage * newImage = UIGraphicsGetImageFromCurrentImageContext();
//结束上下文
UIGraphicsEndImageContext();

NSString * bigThumb = [documentsDirectory stringByAppendingPathComponent:@category_thumb_imagename.jpeg];

NSData * data1 = UIImageJPEGRepresentation(newImage,0.5);
BOOL status1 = [data1 writeToFile:bigThumb atomically:YES];
//好的,现在在主队列中做UI内容

[[NSOperationQueue mainQueue] addOperationWithBlock:^
{
[self setCategoryImageName:bigThumb];
[self.imgCategory setImage:pickedImage];

if(status1){
isAddingCategoryImage = YES;
}

[appDelegate stopLandscapeLoading];

if(error!= nil){
NSLog(@Error:%@,error);
返回;
}

if([self.popoverController isPopoverVisible]){
[self.popoverController dismissPopoverAnimated:true];

}

[picker.view removeFromSuperview];
}];
}];

}



谢谢和问候,
ALOK


i have iPhone application. In some cases, when the device is getting low on free memory, some actions (for example, opening the camera) might cause the application to crash.

My question is that:

  1. I want to prevent these crashes, what is the common way applications do such thing (blocking specific actions, notifying the user, other ideas)? I ask because i didn't encountered such behaviour in iOS applications i ran into.
  2. Are there any ways of preventing such crashes and remain full app functionality, such as iOS system calls to free more memory and etc.? if anyone has best practice or good heuristic i would love to hear about it.

EDIT: I ask this question assuming i already implement the 'didReceiveMemoryWarning' function and freed all the memory i can.

EDIT 2: my app is about pictures. A lot like camera scanner apps, this app allows taking pictures, image processing and saving data about them in memory. my crashes usually happens when i scan a lot of pictures.

解决方案

Some thumb rules i follow:

  1. Using Arc

  2. Use weak for iboutlets (except top level example: UIwindow) and for delegates

  3. Use Strong for class properties and copy for NSString.

  4. Dont access variables directly, use self....way.

  5. Dont use autorelease way of creating new objects, example NSArray *array = [NSArray arrayWithObjects......., instead use NSArray *array = [NSArray alloc] initWit....

    Same way for NSString class. try to use [NSString alloc] initWithFormat..... instead of [NSString stringWithFormat.

  6. When ever you are adding NSNotification(addObserver...) centre must remove(removeObserver..) them in dealloc.

  7. Implement didReceiveMemoryWarning(view controller level) or applicationDidReceiveMemoryWarning(application level and it is called first than view controller level) properly, how ever there are times when you only and only wish to save from crash.you can display an alert telling user less memory available, you can pop/present ..user to home screen.(Bad practice).

  8. Dont perform any manipulation on main thread while being in background thread.Always use @autorelease block for background threads.

  9. use GCD/NSOperation queue for long running processes.

  10. Keep an sharp eye on image resources you are using, use image only of desired size not scale big image to small image size for your need.

  11. USE autorelease pool for long running loops, which create a lot of autoreleased objects.

i have some code snippet for you which ypu can follow:

    //way 1 all on main thread bad approach, basically we are just doing some image manipulation on main thread(should not do on main thread :)) 
-(void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info{

    YourApplicationDelegate *appDelegate = (YourApplicationDelegate *)[[UIApplication sharedApplication]delegate];
    [appDelegate showLandscapeLoading];//think it as progress view/loader

    UIImage *pickedImage = [info objectForKey:UIImagePickerControllerOriginalImage];
    NSData *imageData = UIImagePNGRepresentation(pickedImage);

    NSString *documentsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
    NSString *path = [documentsDirectory stringByAppendingPathComponent:@"category_imagename.jpeg"];

    NSError * error = nil;
//from here
    [imageData writeToFile:path options:NSDataWritingAtomic error:&error];

    **//the important part for discussion UI manipulation on main thread bad bad bad**
    CGSize size1;//A
    size1.width = 400;
    size1.height = 400;
    UIGraphicsBeginImageContext(size1);
    [pickedImage drawInRect:CGRectMake(0, 0, size1.width, size1.height)];
    UIImage *bigImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    NSString *bigThumb = [documentsDirectory stringByAppendingPathComponent:@"category_thumb_imagename.jpeg"];

    NSData *data1=UIImageJPEGRepresentation(bigImage, 0.5);
    BOOL status1=[data1 writeToFile:bigThumb atomically:YES];
    **//up to here should be in non ui thread/seperate thread**
**//below code should go in main thread**
    NSLog(@"status1 -> %d",status1);
    [self setCategoryImageName:bigImage];
    [self.imgCategory setImage:pickedImage];

    if (status1) {
        isAddingCategoryImage = YES;
    }

    [appDelegate stopLandscapeLoading];

    if (error != nil) {
        NSLog(@"Error: %@", error);
        return;
    }

    if ([self.popoverController isPopoverVisible]) {
        [self.popoverController dismissPopoverAnimated:true];

    }

    [picker.view removeFromSuperview];

}

The correct way:

Using NSOperation:

-(void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info{

YourApplicationDelegate *appDelegate = (YourApplicationDelegate *)[[UIApplication sharedApplication]delegate];
[appDelegate showLandscapeLoading];

UIImage *pickedImage = [info objectForKey:UIImagePickerControllerOriginalImage];

NSString *documentsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];

NSError * error = nil;



NSOperationQueue *opQueue = [[NSOperationQueue alloc] init];
[opQueue addOperationWithBlock:^
 {
     // Create a graphics image context very slow stuff
     CGSize newSize = CGSizeMake(400, 400);
     UIGraphicsBeginImageContext(newSize);
     // Tell the old image to draw in this new context, with the desired
     // new size
     [pickedImage drawInRect:CGRectMake(0,0,newSize.width,newSize.height)];
     // Get the new image from the context
     UIImage* newImage = UIGraphicsGetImageFromCurrentImageContext();
     // End the context
     UIGraphicsEndImageContext();

     NSString *bigThumb = [documentsDirectory stringByAppendingPathComponent:@"category_thumb_imagename.jpeg"];

     NSData *data1=UIImageJPEGRepresentation(newImage, 0.5);
     BOOL status1=[data1 writeToFile:bigThumb atomically:YES];
     // ok, now do UI stuff in the main queue

     [[NSOperationQueue mainQueue] addOperationWithBlock:^
      {
          [self setCategoryImageName:bigThumb];
          [self.imgCategory setImage:pickedImage];

          if (status1) {
              isAddingCategoryImage = YES;
          }

          [appDelegate stopLandscapeLoading];

          if (error != nil) {
              NSLog(@"Error: %@", error);
              return;
          }

          if ([self.popoverController isPopoverVisible]) {
              [self.popoverController dismissPopoverAnimated:true];

          }

          [picker.view removeFromSuperview];
      }];
 }];

}

thanks and regards, ALOK

这篇关于内存不足时的iOS应用程序行为的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆