使用UIImagePickerController保存的图像进行内存管理

时间:2012-05-12 12:10:55

标签: objective-c ios cocoa-touch memory uiimagepickercontroller

我正在编写一个应用程序,其中用户自己拍摄照片,然后通过一系列视图使用导航控制器调整图像。如果用户使用前置摄像头拍摄照片(在支持它的设备上设置为默认设置),则此功能完全正常,但是当我重复此过程时,我会大约一半通过并在发出内存警告后崩溃。

在使用仪器进行分析后,我发现当使用较低分辨率的前置摄像头图像时,我的应用程序内存占用大约为20-25 MB,但使用后置摄像头时,每次视图更改都会增加33 MB左右,直到它崩溃为止。 350 MB(在4S上)

下面是我用来处理将照片保存到文档目录,然后读取该文件位置以将图像设置为UIImageView的代码。此代码的“读取”部分通过多个视图控制器(viewDidLoad)进行回收,以便在我去的时候将我保存的图像设置为每个视图中的背景图像。

我已经删除了所有的图片修改代码,将其剥离到试图隔离问题的最小值,我似乎无法找到它。现在,所有应用程序都会在第一个视图中拍摄照片,然后将该照片用作大约10个视图的背景图像,并在用户浏览视图堆栈时进行分配。

现在显然更高分辨率的照片会占用更多内存,但我不明白的是,为什么低分辨率照片似乎没有使用越来越多的内存,而高分辨率照片不断使用越来越多,直到崩溃。

我如何保存并阅读图像:

- (void) imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
{
    UIImage *image = [info objectForKey:@"UIImagePickerControllerOriginalImage"];    
    jpgData = UIImageJPEGRepresentation(image, 1);

    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);  
    NSString *documentsPath = [paths objectAtIndex:0];
    filePath = [documentsPath stringByAppendingPathComponent:@"image.jpeg"];
    [jpgData writeToFile:filePath atomically:YES];    

    [self dismissModalViewControllerAnimated:YES];
    [disableNextButton setEnabled:YES];

    jpgData = [NSData dataWithContentsOfFile:filePath];
    UIImage *image2 = [UIImage imageWithData:jpgData];
    [imageView setImage:image2];
}

现在我知道我可以在保存之前尝试缩放图像,我打算在接下来查看,但我不明白为什么这不起作用。也许我错误地认为ARC在离开堆栈顶部时会自动释放视图及其子视图。

任何人都可以了解为什么我要存储设备内存? (希望简单的事情,我完全忽略了)我是不是设法把ARC抛到了窗外?

编辑:我如何在其他观看中调用图像

- (void)loadBackground
{
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);  
    NSString *documentsPath = [paths objectAtIndex:0];
    NSString *filePath = [documentsPath stringByAppendingPathComponent:@"image.jpeg"];
    UIImage *image = [UIImage imageWithContentsOfFile:filePath];
    [backgroundImageView setImage:image];

}

如何建立视图控制器之间的导航:

enter image description here

编辑2:

我的基本声明是什么样的:

#import <UIKit/UIKit.h>
#import <AVFoundation/AVFoundation.h>
@interface PhotoPickerViewController : UIViewController <UIImagePickerControllerDelegate, UINavigationControllerDelegate>
{
    IBOutlet UIImageView *imageView;
    NSData *jpgData;
    NSString *filePath;
    UIImagePickerController *imagePicker;
    IBOutlet UIBarButtonItem *disableNextButton;
}


@end

如果相关,我如何调用我的图片选择器:

- (void)callCameraPicker
{


    if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera] == YES)
    {
        NSLog(@"Camera is available and ready");

        imagePicker.sourceType =  UIImagePickerControllerSourceTypeCamera;
        imagePicker.delegate = self;
        imagePicker.allowsEditing = NO;
        imagePicker.cameraCaptureMode = UIImagePickerControllerCameraCaptureModePhoto;

        NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo]; for (AVCaptureDevice *device in devices) 
        {
            if([[UIScreen mainScreen] respondsToSelector:@selector(scale)] && [[UIScreen mainScreen] scale] == 2.0) 
            {
                imagePicker.cameraDevice = UIImagePickerControllerCameraDeviceFront;


            }
        }


        imagePicker.modalTransitionStyle = UIModalTransitionStyleCoverVertical;

        [self presentModalViewController:imagePicker animated:YES];

    }


    else
    {
        NSLog(@"Camera is not available");
        UIAlertView *cameraAlert = [[UIAlertView alloc] initWithTitle:@"Error" 
                                                              message:@"Your device doesn't seem to have a camera!" 
                                                             delegate:self cancelButtonTitle:@"Dismiss" 
                                                    otherButtonTitles:nil];
        [cameraAlert show];

    }
}

编辑3:我记录了viewDidUnload,事实上它没有被调用,所以我现在正在调用loadBackground中的viewWillAppear并制作我的backgroundImageViewviewDidDisappear中没有。我希望这会有所帮助,但没有任何区别。

- (void)viewWillAppear:(BOOL)animated
{
    [self loadBackground];
}


- (void)viewDidDisappear:(BOOL)animated
{
    NSLog(@"ViewDidDisappear");
    backgroundImageView = nil;
}

3 个答案:

答案 0 :(得分:6)

UIImageUIImageView之间的关系并不一定适合所有人。

UIImage是图像数据的高级表示 - 仅此一项,它在显示数据方面没有任何作用。

UIImageViewUIImage配合使用,可以显示图片。

没有理由为UIImageView的多个实例无法显示相同的UIImage。这是非常有效的,因为只有一个内存中的图像数据表示,由多个视图共享。

您似乎要做的是为每个视图创建一个新的UIImage,方法是从磁盘加载它。所以这在两个方面是一个糟糕的通用设计:一遍又一遍地实例化实际上相同的UIImage,并重复从磁盘重新加载相同的图像数据。

您的内存问题实际上是一个单独的问题,您没有正确发布加载到UIImage个对象和UIImageViews的图像数据。

理论上,您应该能够从UIImage获取第一个UIImagePickerController,并将该引用传递给您的视图,而无需从磁盘重新加载。

如果您需要从磁盘保存和重新加载,因为更高级别的功能要求(例如,因为用户正在更改图像而您想继续保存),您需要确保完全撕裂通过将其从视图层次结构中删除,将其放在上一个UIView上。在dealloc方法中为视图设置断点以确认其被删除和取消分配是有帮助的,并确保设置对子视图的任何iVar引用(看起来您的backgroundImageView是一个iVar)设定为零。如果您没有正确删除backgroundImageView,则会继续保留对您设置为图片属性的UIImage的引用。

答案 1 :(得分:1)

关于您发布的代码,有几件事好奇

  1. 您的所有视图回调实现都没有调用super这很糟糕!请确保您在viewDidUnload和(如果您已实施)didReceiveMemoryWarning调用超级。
  2. 确保以有意义的方式实施didReceiveMemoryWarning
  3. 真的不应该一遍又一遍地重新创建该图片!我假设您没有编辑实际图像,因为您使用JPEG压缩 - 即使质量为100% - 每次保存都会使图像质量下降......
  4. 检查viewDidUnload的实施情况,确保将每个IBOutlet设置为nil
  5. ARC不是Pixie Dust™!它只是为您节省了一些打字,它让您无需设计和维护对象图!

    根据您的问题,我至少看到这些图表引用了您的图片:

    image 1 <- image-view 1 <- view-controller 1 <- navigation-controller <- key window <- application
    
    image 1 <- image-view 1 <- view 1 <- view-controller 1 <- navigation-controller <- key window <- application
    

    对于在视图控制器,视图,图像视图和图像上具有索引移位的每个视图控制器,都会重复此操作。虽然你必须有单独的视图,视图控制器的图像视图,但我想不出你想要同一图像的多个副本的原因。

    因此,你的内存消耗的第一个斧头显然是不再创建相同图像数据的所有副本 - 我估计这将为你节省一半的低挂起内存。

    接下来就是ARC只能释放对象消耗的内存(如果不再引用它)。

    内存方面,视图并不是完全轻量级的对象,当你构建一个深度导航堆栈时,你最终会看到它们。

    因此,您还需要对这些视图进行任何不必要的强引用。

    ,必须发生这种情况的是视图控制器。此应该发生的最新时间位于视图控制器的viewDidUnload实现中。

    为什么是视图控制器?

    根据你的描述,图像本身仅由UIImageView引用 - 这是一个糟糕的选择,恕我直言,但我离题了...... UIViewController旨在“知道”,当需要查看时以及处理它时是否安全 - 这就是它实现didReceiveMemoryWarningviewDidUnload的原因:

    如果内存压力变高并且视图控制器的视图不在“屏幕上”,didReceiveMemoryWorning的根实现将放开其视图并随后调用viewDidUnload。< / p>

    这就是必须这两种方法的实施中调用super的原因。

    此外,这就是为什么如果你有强大的IBOutlet引用视图控制器视图的子视图,你必须viewDidUnload中取消它们,否则系统无法回收它们的内存占据。

    它的核心UIViewController是一个大型有限状态机。所有这些“某事 - 将要做的事”或“做出任何事”回调用于在这些状态之间进行转换,而大多数默认实现都会进行一些非常重要的记账,以保持所有状态的顺序。

    如果你没有在你的覆盖中调用它们,那么你最终会处于不一致的状态,并且会发生坏事 - 比如内存崩溃 - 会发生。

答案 2 :(得分:0)

只需创建单独的文件夹并将Capture图像保存在其中。成功操作后,使用nsfilemager清除该文件夹数据(或)文件夹。