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:
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.
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:
Using Arc
Use weak for iboutlets (except top level example: UIwindow) and for delegates
Use Strong for class properties and copy for NSString.
Dont access variables directly, use self....way.
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.
When ever you are adding NSNotification(addObserver...) centre must remove(removeObserver..) them in dealloc.
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).
Dont perform any manipulation on main thread while being in background thread.Always use #autorelease block for background threads.
use GCD/NSOperation queue for long running processes.
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.
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
If you use non arc and You had allocated the many object and you did not release these object so it shows the memory problem. you relase all object in dealloc method.In goes upper product option and choose the Analyze. you will see where your application memory leak
If you have used old xcode and you have use new iphone simulator than it shows the memory leak
If you use arc than please comment the autorelease or [obj release] close.
Further than if you want to check their application than side corner button to hold and choose profile. it will show instruments tools. you can enable Nszombies. than you can check how to object values have take and you can see where the memory leak in your application.
No there are is no direct call to free RAM memory in iOS. If you use ARC in your project, define your properties as weak/strong etc correctly and have checked your application for memory leaks or zombie processes there will not be a RAM issue.
iOS frees up memory from other apps to allocate it it to the foreground app if needed and you should not try to deal with it. If you app crashes due to memory issues, you probably have a memory leak in your application. Use Instruments to profile your app.
The memory warning system had a lot of improvements since I started to develop for iOS, ARC also works great helping developers managing memory.
You should profile your app using leaks and allocations to see why your app is consuming so much memory.
Which kind of application are you developing? should be a high memory usage application such as games, or photos app?
A crash could be due to a not well managed answer to a memory warning or to a huge memory occupation that doesn't leave any last breath to your app.
The most common reason are pictures. These devices can't handle a lot of hires resources if you don't manage those situations in the right way, the memory footprint of your app grows until it can't free enough memory.
You need to give more details, though.
Related
Currently I am facing a memory issue problem in building iOS app. I checked for Memory leaks using Instruments. I found that there is one kind of leaks that keeps on showing up named swift_slowAlloc, which I don't have idea about. An snippet of the error is given below.
Another reason I think could happen is due to loading of several UIImages in my app. Just to provide a background, I take various portions of an original image in my app and do some processing on them. However, I don't need to keep the images for further calculations. I used autoreleasepool to release the UIImage; but I doubt that it is working. An example is given below:
#autoreleasepool {
UIImage *imageResized = MatToUIImage(resized28);
// MARK: Send resized28 to CNN and get the output. Fill the dict then
NSString *CNNScore;
CNNScore = [myclass CNNfloat:imageResized W1:W1 W2:W2 Wf1:Wf1 Wf2:Wf2 B1:B1 B2:B2 Bf1:Bf1 Bf2:Bf2];
imageResized = nil;
xtn = [NSNumber numberWithInteger:xt];
xbn = [NSNumber numberWithInteger:xb];
ytn = [NSNumber numberWithInteger:yt];
ybn = [NSNumber numberWithInteger:yb];
symbol = [NSString stringWithFormat:#"%#", CNNScore];
symtype = [NSString stringWithFormat:#"%#", [scoreDic objectForKey: symbol]];
numberInDict = [NSString stringWithFormat:#"%i", n];
inToMaroof = [NSArray arrayWithObjects: xtn, xbn, ytn, ybn, symbol,symtype, nil];
[toMaroof setObject: inToMaroof
forKey: numberInDict];
}
}
Can someone suggest anything on this issue?
These are some possible causes that may cause a memory leak in your program even if using ARC:
You set a strong reference to a parent in a child object. This causes a retain cycle.
You set a strong reference to a delegate in an interface.
You forgot to release an object when you do a toll-free bridging after transferring ownership.
You forgot to set a weak reference to objects you passed in a block.
Here is the code below. It downloads a thumbnail image and then tries to create an image based on the thumbnail file path. But it gives me EXC_BAD_ACCESS error at method call "imageWithContentsOfFile". While EXC_BAD_ACCESS addresses the code trying to access an object that has been released most likely I don't know which object it could be. Any help'd be appreciated!
NSBlockOperation *completionOperation = [NSBlockOperation blockOperationWithBlock:^{
if([[NSFileManager defaultManager] fileExistsAtPath:operation.destinationPath ]){
NSString *key = [[MEURLCacheKeyRegister sharedRegister] cacheKeyForURL:operation.fileUrl];
UIImage *image = [UIImage imageWithContentsOfFile:operation.destinationPath];
}else{
DDLogDebug(#"Thumbnail file doesn't exist at %#", operation.destinationPath);
}
}
}];
AFDownloadRequestOperation *requestOperation = [FileServerDownloadUtils downloadOperationForURL:operation.fileUrl
destinationPath:operation.destinationPath
completion:completionOperation];
[self.fileSyncQueue addOperation:requestOperation];
EXC_BAD_ACCESS indicates that object has been released while its being accessed.
If I were you, I would try following things:
Save the file with .jpg and not .jpg.prv.jpg extension.
Try using initWithContentsOfFile instead of imageWithContentsOfFile as imageWithContentsOfFile autoreleases image which in rare edge cases creates crashes like this.
When passing code to a block, access object properties by making weak reference to self. Something like this: __weak MyController *weakSelf = self. Then use weakSelf to access properties inside the block.
These are just few clues which may help you digging it further. You may use NSZombie and other profiling tools to nail it down.
We all know about the mysterious behind-the-scenes caching mechanism of UIImage's imageNamed: method. In Apple's UIImage Class Reference it says:
In low-memory situations, image data may be purged from a UIImage object to free up memory on the system. This purging behavior affects only the image data stored internally by the UIImage object and not the object itself. When you attempt to draw an image whose data has been purged, the image object automatically reloads the data from its original file. This extra load step, however, may incur a small performance penalty.
In fact, image data will not be "purged from a UIImage object to free up memory on the system" as the documentation suggests, however. Instead, the app receives memory warnings until it quits "due to memory pressure".
EDIT: When using the conventional image file references in your Xcode project, the UIImage caching works fine. It's just when you transition to Asset Catalogs that the memory is never released.
I implemented a UIScrollView with a couple of UIImageViews to scroll through a long list of images. When scrolling, the next images are being loaded and assigned to the UIImageView's image property, removing the strong link to the UIImage it has been holding previously.
Because of imageNamed:'s caching mechanism, I quickly run out of memory, though, and the app terminates with around 170 MB memory allocated.
Of course there are plenty of interesting solutions around to implement custom caching mechanisms, including overriding the imageNamed: class method in a category. Often, the class method imageWithContentOfFile: that does not cache the image data is used instead, as even suggested by Apple developers at the WWDC 2011.
These solutions work fine for regular image files, although you have to get the path and file extension which is not quite as elegant as I would like it to be.
I am using the new Asset Catalogs introduced in Xcode 5, though, to make use of the mechanisms of conditionally loading images depending on the device and the efficient image file storage. As of now, there seems to be no straight forward way to load an image from an Asset Catalog without using imageNamed:, unless I am missing an obvious solution.
Do you guys have figured out a UIImage caching mechanism with Asset Catalogs?
I would like to implement a category on UIImage similar to the following:
static NSCache *_cache = nil;
#implementation UIImage (Caching)
+ (UIImage *)cachedImageNamed:(NSString *)name {
if (!_cache) _cache = [[NSCache alloc] init];
if (![_cache objectForKey:name]) {
UIImage *image = ???; // load image from Asset Catalog without internal caching mechanism
[_cache setObject:image forKey:name];
}
return [_cache objectForKey:name];
}
+ (void)emptyCache {
[_cache removeAllObjects];
}
#end
Even better would of course be a way to have more control over UIImage's internal cache and the possibility to purge image data on low memory conditions as described in the documentation when using Asset Catalogs.
Thank you for reading and I look forward to your ideas!
UPDATE: Cache eviction works fines (at least since iOS 8.3).
I am running into the same issue (iOS 7.1.1) and I kind of though that #Lukas might be right
There is a high probability that the mistake is not inside Apple's ... caching but in your .. code.
Therefore I have written a very simple Test App (view full source below) where I still see the issue. If you see anything wrong with it, please let the me know about it. I know that it really depends on the image sizes. I only see the issue on an iPad Retina.
#interface ViewController ()
#property (nonatomic, strong) UIImageView *imageView;
#property (nonatomic, strong) NSArray *imageArray;
#property (nonatomic) NSUInteger counter;
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.imageArray = #[#"img1", ... , #"img568"];
self.counter = 0;
UIImage *image = [UIImage imageNamed:[self.imageArray objectAtIndex:self.counter]];
self.imageView = [[UIImageView alloc] initWithImage: image];
[self.view addSubview: self.imageView];
[self performSelector:#selector(loadNextImage) withObject:nil afterDelay:1];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
NSLog(#"WARN: %s", __PRETTY_FUNCTION__);
}
- (void)loadNextImage{
self.counter++;
if (self.counter < [self.imageArray count])
{
NSLog(#"INFO: %s - %lu - %#",
__PRETTY_FUNCTION__,
(unsigned long)self.counter,
[self.imageArray objectAtIndex:self.counter]);
UIImage *image = [UIImage imageNamed:[self.imageArray objectAtIndex:self.counter]];
self.imageView.frame = CGRectMake(0, 0, image.size.width, image.size.height);
[self.imageView setImage:image];
[self performSelector:#selector(loadNextImage) withObject:nil afterDelay:0.2];
} else
{
NSLog(#"INFO: %s %#", __PRETTY_FUNCTION__, #"finished");
[self.imageView removeFromSuperview];
}
}
#end
Inplace Implementation
I wrote some code to keep the image asset but load it with imageWithData: or imageWithContentsOfFile: use xcassets without imageNamed to prevent memory problems?
My text fields and my images picked from image picker all reset set blank if my app stops running, or device is turned off, How can I retain this information?
I've used a singleton (with help from a fellow member) and I can retain my image...that is until the app is killed or device is turned off. Then it's gone.
.m
- (void)viewDidLoad
{
singletonObj = [Singleton sharedSingletonController];
imageView.image = singletonObj.imagePicked;
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)viewDidUnload
{
[self setImageView:nil];
[super viewDidUnload];
// Release any retained subviews of the main view.
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
}
#pragma mark - Action
- (IBAction)done:(id)sender
{
[self.delegate flipsideViewControllerDidFinish:self];
}
- (IBAction)btn:(id)sender {
UIImagePickerController * picker = [[UIImagePickerController alloc] init];
picker.delegate = self;
if((UIButton *) sender == choosePhotoBtn) {
picker.sourceType = UIImagePickerControllerSourceTypeSavedPhotosAlbum;
}
[self presentModalViewController:picker animated:YES];
}
- (void)imagePickerController:(UIImagePickerController*)picker didFinishPickingMediaWithInfo:(NSDictionary*)info
{
NSData *dataImage = UIImageJPEGRepresentation([info objectForKey:#"UIImagePickerControllerOriginalImage"],1);
UIImage *img = [[UIImage alloc] initWithData:dataImage];
singletonObj.imagePicked = img;
imageView.image = img;
[picker dismissModalViewControllerAnimated:YES];
}
#end
There are two types of memory: volatile (RAM) and permanent memory (ie: hard drives and other storage).
Volatile memory is cleared and lost when a program/computer shuts down.
Using a singleton is fine but it's completely unrelated to keeping data from session to session (and by session I mean the time when the program is running: from launch to termination of an application).
You need to store data you wish to keep from session to session to file using any method you want. Depending on the information you want to store, there are different dedicated mechanism for saving:
(such as NSUserDefaults for user preferences).
Core Data is a framework which defines mechanism for structuring data and saving/reading it to file (a.k.a. persistent store).
You can also use serialization.
Or you can always manually manipulate files.
NSData has writeToFile:atomically: which will write create a file out of a data object. If you want to save an image, you must obtain an UIImage's underlying data (i.e.: UIImagePNGRepresentation(...)).
You are going to have to use core data. It can accept NSData from a UIIimage as well as NSStrings. The function within the appDelegate, AppicationWillTerminate, will have to use so that the information is stored just before the application is terminated.
This is going to require some decent amount of work to get it working properly, but nothing too difficult. If you need help understanding core data, I recommend this link
http://www.raywenderlich.com/934/core-data-on-ios-5-tutorial-getting-started
Any idea why this crashes? What am I doing wrong?
Thanks!
-(IBAction)animationOneStart {
NSMutableArray* arrayOfImages = [[NSMutableArray alloc]initWithCapacity:10];
for(int count = 1; count <= 22; count++)
{
NSString *path = [NSString stringWithFormat:#"testDance3_%03d.jpg", count];
UIImage* img = [[UIImage alloc]initWithContentsOfFile:path];
[arrayOfImages addObject:img];
[img release];
}
loadingImageView.animationImages = arrayOfImages;
[arrayOfImages release];
loadingImageView.animationDuration = 3;
loadingImageView.animationRepeatCount = 1; //Repeats indefinitely
[loadingImageView startAnimating];
loadingImageView.animationImages = nil;
[loadingImageView release];
}
Try initializing your NSMutableDictionary with capacity for the same quantity of object's you'll be storing in it. If that doesn't work, I'd try to comment out these two lines:
//loadingImageView.animationImages = nil;
//[loadingImageView release];
In the scope of this code, your other calls appear balanced. But we can't see what's happening inside loadingImageView, and so my guess is that either the loadingImageView itself or it's animations are being released prematurely.
I can't see enough code confirm this, but a couple things that are suspicious are here:
[loadingImageView startAnimating];
loadingImageView.animationImages = nil;
[loadingImageView release];
So, while the animation is running, you are releasing the images that are being animated? Or the view which, itself, is animating? Probably one or the other or both is the problem.
If the animation is supposed to run indefinitely, you are going to need to keep the view around indefinitely. And if it stops eventually, you should release it after it stops.
You call to addObject will fail when nil is passed. The UIImage could be nil when your system run out of memory and cannot allocate more. When that happens depends on how large your images are, but basically you can be sure that sooner of later use of loadingImageView.animationImages combined with allocating all your UIImage objects at the same time will cause your app to crash. See also playing-looping-images-sequence-in-uiview. You basically need a different approach that does not hold all the images in memory at the same time, the uncompressed images consume way too much memory.