Memory management with saving images from UIImagePickerController

I am writing an application in which the user takes pictures of them on their own, and then looks through a series of views to adjust the image using the navigation controller. This works great if the user takes a photo with a front camera (installed by default on devices that support it), but when I repeat the process, I get about half the way, and it crashes after a warning about a memory warning.

After profiling in the Tools, I see that the memory size of my applications is about 20-25 MB when using the low-resolution front camera image, but when using the rear camera, each change in view adds another 33 MB or so until it works about 350 MB (on 4S)

Below is the code that I use to control the saving of the photo in the document directory and then reading this file to set the image to UIImageView . Part of the β€œread” of this code is processed through several view controllers (viewDidLoad) to set the image that I saved as a background image in each view when I go.

I removed all the image modification code in order to break this down to a minimum, trying to isolate the problem, and I cannot find it. Currently, the entire application takes pictures at a glance, and then uses this photo as a background image for more than 10 views, highlighting when the user passes through the presentation stack.

Now, obviously, higher resolution photos will use more memory, but I don’t understand why low resolution photos do not seem to use more memory as I go, while high resolution photos constantly use more and more before the failure.

How do I save and read an image:

 - (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]; } 

Now I know that I can try to scale the image before I save it, and I plan to look further, but I do not understand why this does not work as it is. Perhaps I was mistaken under the impression that ARC automatically frees up looks and their sub-items when they leave the top of the stack.

Can anyone shed some light on why I am in the memory storage of my devices? (Hope something simple, I completely ignore). Somehow I managed to throw ARC out of the window?

EDIT: How I invoke an image in my other views

 - (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]; } 

How to set navigation between my view controllers:

enter image description here

EDIT 2:

What my main ads look like:

 #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 

If appropriate, how do I call my image picker:

 - (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]; } } 

EDIT 3: I registered viewDidUnload and didn't actually call it, so I call loadBackground in viewWillAppear and create backgroundImageView nil in viewDidDisappear . I expected this to help, but it does not make any difference.

 - (void)viewWillAppear:(BOOL)animated { [self loadBackground]; } - (void)viewDidDisappear:(BOOL)animated { NSLog(@"ViewDidDisappear"); backgroundImageView = nil; } 
+7
source share
3 answers

The relationship between UIImage and UIImageView not necessarily intuitive for everyone.

UIImage is a high level presentation of your image data - it does nothing in terms of displaying data.

UIImageView works with UIImage so you can display an image.

There is no reason why multiple instances of UIImageView cannot display the same UIImage . This is good and effective because there is only one representation of the image data in memory shared by several views.

It looks like you are creating a new UIImage for each of your views, loading it from disk. Thus, this is a poor overall design in two respects: re-creating what is actually the same UIImage again and again, and re-loading the same image data from disk several times.

The memory issue is really a separate issue when you incorrectly publish image data that you load into UIImage and UIImageViews .

In theory, you should be able to take the first UIImage that you get from the UIImagePickerController and just pass this link around your views without rebooting from disk.

If you need to save and reboot from disk due to higher functional requirements (for example, because the image is modified by the user and you want to save it), you need to be sure that you completely tear down the previous UIView by removing it from the view hierarchy. It is useful to set a breakpoint in the dealloc method so that the view confirms that it is being deleted and canceled, and make sure you set any iVar links to iVar (it seems that your backgroundImageView is an iVar) set to zero. If you do not properly tear yourself away from this backgroundImageView , it continues to hold the link to the UIImage set for this image property.

+6
source

There are a few things you are interested in regarding the code you posted:

  • None of your callback implementations call super . Poorly! Do it once again that you call super in viewDidUnload and (if you implemented it) didReceiveMemoryWarning .
  • Be sure to implement didReceiveMemoryWarning meaningful way!
  • You really should not re-create this image again and again! I assume that you are not editing the actual image, because it uses JPEG compression, which even with 100% quality will degrade your image every time you save ...
  • Check your implementation of viewDidUnload to set each of your IBOutlets to nil .

ARC is not Pixie Dust β„’! It just saves you from typing, it does not free you from designing and supporting your object graphs!

From your question, I see at least these graphs that apply to your image:

 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 

This is repeated for each view controller with an index shift on the view controller, view, image view, and image. Although you should have separate views, image views for your view controllers, I can't figure out why you need multiple copies of the same image.

Thus, the first ax in memory consumption clearly does not allow you to create all these copies of the same image data. The identifier estimates that this will allow you to get half the memory savings.

The next thing is that ARC can only free the memory consumed by your objects if it is no longer referenced.

Reasonable memory, representations are not really light objects, and when you create a deep navigation stack, you end up with them.

Thus, you need to omit any unnecessary references to these views.

The level at which this is to happen is the view controller. The last time this should happen is the implementation of viewDidUnload view controllers.

Why view manager?

From what you described, the image itself refers only to UIImageView - this is a bad choice, IMHO, but I'm distracted ...
UIViewController designed to β€œknow” when its presentation is necessary and when it is safe to dispose of it - therefore it implements didReceiveMemoryWarning and viewDidUnload :

If the memory pressure reaches its maximum and the view controllers are not displayed on the screen, the root implementation of didReceiveMemoryWorning release its representation and call viewDidUnload after itself.

This is why you should go to super in your implementations of both of these methods.

In addition, that is why if you have strong IBOutlets that relate to views of view-controllers, you must use them in viewDidUnload or the system will not be able to return the memory they occupy.

The UIViewController is based on the final UIViewController machine. All of these "something-will / did-whatever" callbacks are used to transition between these states and most default implementations do very important accounting to keep it all in order.

If you do not call them in your redefinitions, you will find yourself in inconsistent states and bad things - for example, this is due to lack of memory - it will happen.

+1
source

Just create a separate folder and save Capture images in it. After a successful operation, clear the folder (s) of the folder using nsfilemager.

0
source

All Articles