【问题标题】:iOS application behaviour in case of low memoryiOS 应用程序在内存不足的情况下的行为
【发布时间】:2013-12-18 10:12:09
【问题描述】:

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

我的问题是:

  1. 我想防止这些崩溃,应用程序的常用方式是什么 做这样的事情(阻止特定的动作,通知用户,其他 想法)?我问是因为我在 iOS 中没有遇到过这样的行为 我遇到的应用程序。
  2. 是否有任何方法可以防止此类崩溃并保持完整的应用程序功能,例如 iOS 系统调用以释放更多内存等?如果有人有最佳实践或良好的启发式方法,我很想听听。

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

编辑 2:我的应用是关于图片的。很像相机扫描仪应用程序,此应用程序允许拍照、图像处理并将有关它们的数据保存在内存中。我的崩溃通常发生在我扫描大量图片时。

【问题讨论】:

  • 如果是您的应用程序,您将收到内存警告,然后您可以处理。
  • 您能否从您的部分代码中显示您的一些代码片段,例如:- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info,以便我们帮助您做什么做或不做
  • 你说你“释放了所有你能释放的内存”,但如果它在相机可见时仍然崩溃,那么显然你可以释放更多内存。当相机可见时,旧设备在 RAM 中不能有任何其他内容,您需要虚拟关闭整个应用程序并在相机消失后将其恢复。重新设计您的代码,以便所有内容都可以设置为空。如果它当前在屏幕上不可见,则不需要在 RAM 中。 Xib 文件使此过程更容易,重新加载 Xib 很简单。

标签: ios iphone objective-c memory-management


【解决方案1】:

我遵循的一些经验法则:

  1. 使用弧

  2. 对 iboutlets(除了顶级示例:UIwindow)和委托使用 weak

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

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

  5. 不要使用自动释放方式创建新对象,例如 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. 将自动释放池用于长时间运行的循环,这会创建大量自动释放对象。

我为你准备了一些代码 sn-p,ypu 可以遵循:

    //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];

}

正确方法:

使用 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];
      }];
 }];

}

感谢和问候, 好了

【讨论】:

  • 谢谢。不过有几个问题: 1 - 请解释“4”和“5”部分。 2 - 在主 NSOperationQueue 中做 UI 的东西和在任何队列块之外运行它们有什么不同?
  • 4.通过属性访问ivar(self.)保证更好的内存管理。 5. autoreleased对象是不以alloc]init开头的对象,我已经给你例子了。2-直到我们不在后台调用方法,默认情况下它在main/ui线程上运行。所以情况当你调用一个方法来做一些后台的东西,并从那个上下文(我的意思是从后台)你想要更新(UIKit 控件/主线程)。我使用 [NSOperationQueue mainQueue] 发布的示例只是一个选项,你也可以使用 performSelectorOnMainThread 或 dispatch_async(get_mainqueue)
【解决方案2】:
  1. 如果你使用非 arc 并且你已经分配了很多对象并且你没有释放这些对象,所以它显示了内存问题。您在 dealloc 方法中释放所有对象。进入上层产品选项并选择分析。您将看到您的应用程序内存泄漏的位置

  2. 如果您使用过旧的 xcode,并且您使用过新的 iphone 模拟器,则会显示内存泄漏

  3. 如果您使用 arc,请评论 autorelease 或 [obj release] 关闭。

  4. 如果你想检查他们的应用程序而不是侧角按钮来按住并选择配置文件。它将显示仪器工具。您可以启用 Nszombies。您可以检查对象值的取值方式,并且可以查看应用程序中的内存泄漏位置。

【讨论】:

    【解决方案3】:

    不,iOS 中没有直接调用释放 RAM 内存。如果您在项目中使用 ARC,请将您的属性正确定义为弱/强等,并检查您的应用程序是否存在内存泄漏或僵尸进程,则不会出现 RAM 问题。

    如果需要,iOS 会从其他应用程序释放内存以将其分配给前台应用程序,您不应尝试处理它。如果您的应用程序由于内存问题而崩溃,则您的应用程序中可能存在内存泄漏。使用 Instruments 分析您的应用。

    【讨论】:

      【解决方案4】:

      自从我开始为 iOS 开发以来,内存警告系统有了很多改进,ARC 也可以很好地帮助开发人员管理内存。
      您应该使用泄漏和分配来分析您的应用,以了解您的应用消耗如此多内存的原因。
      您正在开发哪种应用程序?应该是高内存使用的应用程序,例如游戏或照片应用程序?
      崩溃可能是由于对内存警告的管理不善或内存占用过多导致您的应用程序没有最后一口气。 最常见的原因是图片。如果您没有以正确的方式管理这些情况,这些设备将无法处理大量的招聘资源,您的应用程序的内存占用量会不断增长,直到无法释放足够的内存。

      不过,您需要提供更多详细信息。

      【讨论】:

      • 嗨,正如你所建议的 - 我的应用确实是关于图片的。很像相机扫描仪应用程序,此应用程序允许拍照、图像处理并将有关它们的数据保存在内存中。我的崩溃通常发生在我扫描大量图片时
      • 很可能您没有以正确的方式处理它们。您无法在内存中保存大量图像,将它们保存在文件系统中并在需要时加载它们。
      • @Eliktz 如果您正在扫描大量图片,一次只需要 一张 在内存中。所有其他图片不需要在内存中。使用@autoreleasepool和NSCache等内存管理工具来达到这个效果。
      • 澄清一下,我不会把我所有的图像都保存在内存中,而是关于它们的元数据(主要是字符串)。图片存储在缓存中,最多只有一张在内存中
      • 哪个缓存?如果你想释放它们,你应该将它们保存在磁盘上,没有其他解决方案。分析分配并检查您是否真的在释放它们。
      猜你喜欢
      • 2012-06-10
      • 1970-01-01
      • 2015-12-02
      • 2016-02-05
      • 1970-01-01
      • 1970-01-01
      • 2012-04-08
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多