Received Memory Warning iOS - ios

In my split view iPad app the default detail view loads a random image from an array and does this any time the user goes back to that view. The app loads fine with that view and I can go to another view fine. The problem is that if I go back to that view, sometimes it will crash and sometimes it will crash if I select another view after going back to the default view. I do not show any leaks when I run the leaks tool and I don't show anything in the log every time a crash occurs. I did receive a "Received Memory Warning" log once so it the crashes must have something to do with a leak somewhere, I'm just not sure where. I am using ARC. Any ideas?
Here is my viewDidLoad method:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
UIImage *agelity = [UIImage imageNamed:#"Agelity"];
UIImage *agelity2 = [UIImage imageNamed:#"Agelity2"];
UIImage *biltmore = [UIImage imageNamed:#"Biltmore"];
UIImage *biltmore2 = [UIImage imageNamed:#"Biltmore2"];
UIImage *biltmore3 = [UIImage imageNamed:#"Biltmore3"];
UIImage *choice = [UIImage imageNamed:#"Choice"];
UIImage *enterprise = [UIImage imageNamed:#"Enterprise"];
UIImage *enterprise2 = [UIImage imageNamed:#"Enterprise2"];
UIImage *grainger = [UIImage imageNamed:#"Grainger"];
UIImage *grainger2 = [UIImage imageNamed:#"Grainger2"];
UIImage *greatWolf = [UIImage imageNamed:#"Great_Wolf"];
UIImage *greatWolf2 = [UIImage imageNamed:#"Great_Wolf2"];
UIImage *officeDepot = [UIImage imageNamed:#"Office_Depot1"];
UIImage *officeDepot2 = [UIImage imageNamed:#"Office_Depot2"];
UIImage *officeDepot3 = [UIImage imageNamed:#"Office_Depot3"];
UIImage *sams = [UIImage imageNamed:#"Sams"];
UIImage *sams2 = [UIImage imageNamed:#"Sams2"];
NSMutableArray *benefitAds = [[NSMutableArray alloc]initWithObjects:agelity, agelity2, biltmore, biltmore2, biltmore3, choice, enterprise, enterprise2, grainger, grainger2, greatWolf, greatWolf2, officeDepot, officeDepot2, officeDepot3, sams, sams2, nil];
int randomIndex = arc4random() % [benefitAds count];
adImage.image = [benefitAds objectAtIndex:randomIndex];
[self configureView];
}
EDIT: I am trying to use the suggestion of using imageWithData instead of imageNamed so I'm doing this:
NSData *agelityData = [NSData dataWithContentsOfFile:#"Agelity"];
UIImage *agelity = [UIImage imageWithData:agelityData];
but now the app crashes at launch with on the line:
int randomIndex = arc4random() % [benefitAds count];
with:
Thread 1: EXC_ARITHMETIC(code=EXC_I386_DIV, subcode=0x0)
When I run it on my device instead of the simulator, I get this:
Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayM objectAtIndex:]: index 3051310543 beyond bounds for empty array'
EDIT: I set an exception breakpoint because I'm getting an exc_bad_access code=1 error. It looks like the app randomly crashes at times when I'm changing the detail view. I guess I'll create a new question.
Thanks for all the help!

I don't know if it is exactly this that is causing the crash (high chances that it might be), but I really suggest you to don't store all images inside your array.
A better approach would be store the name of the images, and the allocate just one UIImage, with the name selected.
See this:
- (void)viewDidLoad
{
NSMutableArray *benefitAds = [[NSMutableArray alloc]initWithObjects:#"Agelity", #"Agelity2", #"Biltmore", #"Biltmore2", #"Biltmore3", #"Choice", #"Enterprise", #"Enterprise2", #"Grainger", #"Grainger2", #"Great_Wolf", #"Great_Wolf2", #"Office_Depot1", #"Office_Depot2", #"Office_Depot3", #"Sams", #"Sams2", nil];
int randomIndex = arc4random() % [benefitAds count];
if(randomIndex < [benefitAds count]) {
adImage.image = [UIImage imageNamed:[benefitAds objectAtIndex:randomIndex]];
[self configureView];
}
else
{
//error message
}
}
Please give some feedback it worked or not.
EDIT:
Try to check if the random number get is really a valid index before using it.

imageNamed: uses the internal cache which does not empty itself on memory warnings. Try imageWithData

- (void)didReceiveMemoryWarning {
if([self isViewLoaded] && self.view.window == nil) {
self.view = nil;
}
[super didReceiveMemoryWarning];
}

Related

How to preload the images in CollectionView by generating a Thumbnail of the pdf?

I would like to create a pdfReader with a collectionview. What I want is to have a collectionview with Thumbnails of the pdf to display. So I use this in the viewDidLoad (to avoid the fact that it will generate the thumbnail in the collectionview each time we go down or up). It is generated one time, and it is without lag :
Loading the thumbnail of the pdf in viewDidLoad:
- (void)viewDidLoad
...
coverPdf = [NSMutableArray new];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^(void) {
// Load image on a non-ui-blocking thread
NSString *pdfPath = nil;
NSURL *pdfUrl = nil;
CGPDFDocumentRef pdfRef = nil;
NSMutableArray *arr = [NSMutableArray new];
for (id cover in filePathsArray)
{
pdfPath = [categoryPath stringByAppendingPathComponent:cover];
pdfUrl = [NSURL fileURLWithPath:pdfPath];
pdfRef = CGPDFDocumentCreateWithURL((CFURLRef)pdfUrl);
[arr addObject:[self imageFromPDFWithDocumentRef:pdfRef]];
NSLog(#"first process");
}
coverPdf = [NSMutableArray arrayWithArray:arr];
dispatch_sync(dispatch_get_main_queue(), ^(void) {
[pdfCollectionView reloadData];
});
});
...
}
Generating the thumbnail:
- (UIImage *)imageFromPDFWithDocumentRef:(CGPDFDocumentRef)documentRef
{
CGPDFPageRef pageRef = CGPDFDocumentGetPage(documentRef, 1);
CGRect pageRect = CGPDFPageGetBoxRect(pageRef, kCGPDFCropBox);
UIGraphicsBeginImageContext(pageRect.size);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextTranslateCTM(context, CGRectGetMinX(pageRect),CGRectGetMaxY(pageRect));
CGContextScaleCTM(context, 1, -1);
CGContextTranslateCTM(context, -(pageRect.origin.x), -(pageRect.origin.y));
CGContextDrawPDFPage(context, pageRef);
UIImage *finalImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return finalImage;
}
Using the thumbnail:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
ListPdfCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"CollectionViewCell" forIndexPath:indexPath];
cell.productLabel.text = [filePathsArray objectAtIndex:indexPath.row];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^(void) {
// Load image on a non-ui-blocking thread
dispatch_sync(dispatch_get_main_queue(), ^(void) {
// Assign image back on the main thread
if ([coverPdf count] > indexPath.row)
{
cell.pdfImage.image = [coverPdf objectAtIndex:indexPath.row];
}
});
});
return cell;
}
I have two problems with that method :
- The first one is that the thumbnail is taking too much time to appear. When it is loaded, it works well.
- The second problem, is that the memory is continually increasing and even if I close the viewcontroller, and I come in it, it seems that the memory is not released. If I close the viewcontroller and come in 9 or 10 times, if crash the app.
In conclusion, how can I create a collection view by loading the thumbnails of the pdfs in advance and how to avoid a crash with the memory increasing ?
Thanks in advance.
SOLUTION :
For the fact that it takes too much time to appear, I just replaced the DISPATCH_QUEUE_PRIORITY_BACKGROUND with the DISPATCH_QUEUE_PRIORITY_HIGH. It is much better.
For the memory leak, I used the CGPDFDocumentRelease() function just at the end of the loop like this, and all works like a charm :
for (id cover in filePathsArray)
{
pdfPath = [categoryPath stringByAppendingPathComponent:cover];
pdfUrl = [NSURL fileURLWithPath:pdfPath];
pdfRef = CGPDFDocumentCreateWithURL((CFURLRef)pdfUrl);
[arr addObject:[self imageFromPDFWithDocumentRef:pdfRef]];
CGPDFDocumentRelease(pdfRef);//Line added
NSLog(#"first process");
}
I have bad news for you. Memory leak is not your problem but iOS 10 memory management bug
There is a memory management bug in iOS 10.0.1 and 10.0.2 in the CGContextDrawPDFPage() function.
You can find details here http://www.openradar.me/28415289
Also you can find useful this discussion https://github.com/vfr/Reader/issues/166
In few words possible workaround is not to create
CGPDFPageRef pageRef = CGPDFDocumentGetPage(documentRef, 1);
each time but use one CGPDFPageRef for all of your pdf files. Then set it NULL when you not needs it anymore.
Creating thumbnails lag
And in terms of solution for creating thumbnails. I can only suggest not to do it in this VC at all but create thumbnail for each .pdf in the app at the moment when this .pdf added to the app (or on app launch in background service if all pdfs stored in the app bundle). Then you can save this previews as .jpg or .png files with names equal to pdf names or set relationships between pdf and preview in any database or other storage if you have it in your app.
Then just reuse this previews in your collectionView.

UIImage passing between controllers causes memory warnings

I am working on an app that takes an image from the photo library using UIPickerController and then loads it into an UIImageView in the first controller. Then i pass the UIImage via property to the second controller of navigation controller in order to edit the image here. When i pop this EditController tapping on Back and then retake the process passing one more time the image to the EditController, the memory encreases every time and after 5 cycles i receive memory warnings. Is there a solution to this issue?
//Getting the image from the PickerController
_imageToEdit = info[UIImagePickerControllerOriginalImage];
_imageToEdit=[self fixOrientation:_imageToEdit];
[_imageView setImage:_imageToEdit];
//Passing image to EditController
if ([segue.identifier isEqualToString:#"editImage"]) {
EditImageViewController *destViewController=segue.destinationViewController;
destViewController.newlyImage=_imageToEdit;
destViewController.optVC=self;
destViewController.numberReceived=_firstTimeInEdit;
}
I made a new method where I release all the objects I don't need. I don't receive memory warnings anymore.
- (void) backAction {
[_editBaseController removeAllButtons];
_editBaseController = nil;
_undoImage = nil;
copyImage = nil;
_modifiedImage = nil;
_newlyImage = nil;
aCIImage = nil;
context = nil;
free(imageRawData);
free(filterChannelRowData);
_scrollingViewOne = nil;
_scrollingViewTwo = nil;
[self.navigationController popViewControllerAnimated:YES];}

UITableViewCell image load from url

I have a problem with loading an image from an url to display in a table. I currently have the following code to handle the image loading in a class that extends UITableViewCell:
- (void) initWithData:(NSDictionary *) data{
NSDictionary *images = [data objectForKey:#"images"];
__block NSString *poster = [images objectForKey:#"poster"];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
NSURL *posterURL = [[NSURL alloc] initWithString:poster];
NSData *imageData = [NSData dataWithContentsOfURL:posterURL];
if (imageData != nil) {
dispatch_async(dispatch_get_main_queue(), ^{
// 4. Set image in cell
self.backgroundImage.image = [UIImage imageWithData:imageData];
[self setNeedsLayout];
});
}
});
self.backgroundImage.image = [UIImage imageNamed:#"static"];
}
The initWithData method is called from the ViewController in the tableView:cellForRowAtIndexPath: delegate. Everything works as expected until i scroll. From what i read, the TableView cells are recycled and because the images are being loaded async, i get rows with wrong images. Also, the images are not cached and are loaded again whenever the cell is displayed.
Eg: Scroll to the middle and immediately scroll back up. The first cell will have the image that's corresponding to the middle cell that didn't get to finish loading.
Any help or suggestions? Thank you very much :)
First of all as the comment mentioned, I would definitely recommend using an existing framework/component to do this job.
The best candidates are probably:
https://github.com/rs/SDWebImage
https://github.com/enormego/EGOImageLoading
OR if you also want a general networking library
https://github.com/AFNetworking/AFNetworking
That said, if you still want to try it on your own, you would probably want to implement caching with an NSMutableDictionary using the indexPath as the key, and the image as the value.
Assuming you have an initialized instance variable NSMutableDictionary *imageCache
In your cellForRowAtIndexPath method, before attempting to do any image loading, you would check to see if your cache already has an image for this index by doing something like this
if(! imageCache[indexPath])
{
// do your web loading here, then once complete you do
imageCache[indexPath] = // the new loaded image
}
else
{
self.backgroundImage.image = imageCache[indexPath];
}

MPMediaItemPropertyArtwork causes crash (weird issue)

allocations before running 'extra loop'
The code:
// loading items to the array, there are no memory warnings after this is completed. The first allocations screenshot is after this code and before extra loop code.
NSMutableArray *albumCovers = [[NSMutableArray alloc] init];
for (MPMediaQuery *query in queries) {
NSArray *allCollections = [query collections];
for (MPMediaItemCollection *collection in allCollections) {
MPMediaItemArtwork *value = [collection.representativeItem valueForProperty:MPMediaItemPropertyArtwork];
UIImage *image = [value imageWithSize:CGSizeMake(100, 100)];
if (image) {
[albumCovers addObject:image];
}
}
}
}
_mediaCollections = [NSArray arrayWithArray:artworkedCollections];
_albumCovers = [NSArray arrayWithArray:albumCovers];
}
And somewhere else:
// !!!!! extra loop - from here the memory starts to grow and never release
for (i=0; i< 800; i++) {
UIImage * coverImage = [_albumCovers objectAtIndex:indexPath.row];
[veryTemp setImage:coverImage]; // exactly this line adds to the memory. with this line commented, there is no problem.
}
allocations after running 'extra loop'
and to clarify, call stack with only-obj-c on and system libraries off (if i turn them on, the highest % is 0.9% per heaviest method)
I've made some research, and found at stackoverflow, that these VM:ImageIO_PNG_Data are usually comming from [UIImage imageNamed:], however as you can see i don't use this method, i'm just getting the reference from MPMediaItemCollection.
The problem was that UIImage usually keeps just a ref(CGImageRef), which is a small one. After displaying items, CGImageRef was 'injected' with information. As a result the table was growing all the time.
The simple but not the most beautiful solution was to use the code:
NSArray = #[obj1, obj2, obj3]; // where obj is custom NSObject and has a UIImage property
instead of:
NSArray = #[img1, img2, img3]; // where img is of UIImage type
Wrap in #autorelease pool?
Also, why are you setting the image for veryTemp (I assume it's a UIImageView) 800 times in [veryTemp setImage:coverImage]; ?
Finally:
[_albumCovers objectAtIndex:indexPath.row];
You're getting the image object at the exact same index (indexPath.row) in your loop. I am not quite sure what you're trying to achieve in your code?

IOS: UIImage sent to deallocated instance

I have this code:
imageNumber++;
UIImage *image = [[UIImage alloc]init];
image = [UIImage imageNamed:[NSString stringWithFormat:#"l%d_s%d.png", currentSet, imageNumber]];
[imageToColor setImage:image]; //<-- here it crash
[image release];
so, after a few minuted my app crash with this message
[UIImage isKindOfClass:]: message sent to deallocated instance
why?? can you help me?
imageNumber++;
UIImage *image = [UIImage imageNamed:[NSString stringWithFormat:#"l%d_s%d.png", currentSet, imageNumber]];
[imageToColor setImage:image];
The problem with your code is that you allocate a UIImage object on image variable and then without releasing it you set it to another autoreleased object. [UIImage imageNamed:] returns an autoreleased UIImage object.
You are allocating a new image, and then you are assigning that pointer to an autoreleased UIImage returned by UIImage imageNamed:, causing a memory leak, then you try to release an autoreleased object, which get released again, causing the error.
imageNumber++;
image = [UIImage imageNamed:[NSString stringWithFormat:#"l%d_s%d.png", currentSet, imageNumber]];
[imageToColor setImage:image]; //<-- here it crash
it seems like you got it all wrong. At first you alloc-init object and then assign autorelased object to the pointer, making the first object leaking. Here is the correct code:
imageNumber++;
UIImage *image [UIImage imageNamed:[NSString stringWithFormat:#"l%d_s%d.png", currentSet, imageNumber]];
[imageToColor setImage:image];
1) You don't need to allocate an empty image before loading the image from a file.
2) Are you sure that the image specified by the string [NSString stringWithFormat:#"l%d_s%d.png", currentSet, imageNumber] actually exists and is available to the app ?
the imageNamed call will return null if that image cannot be found. Step through in the debugger and see if the image is actually loaded.

Resources