Im trying to implement a simple video stream but for some reason my memory won't get freed:
(void)updateImage:(UIImage *)image{
self.indicator.hidden = TRUE;
//CGImageRelease([self.imageView.image CGImage]);
self.imageView.image = nil;
self.imageView.image = image;
[self.imageView setNeedsDisplay];
}
If I use
CGImageRelease([self.imageView.image CGImage]);
memory will be freed. But when I return to a previous view controller the app will crash as it tries to free the allocated memory for that image, which I already freed. This method is called from a async task which creates an image using:
UIImage *image = [UIImage imageWithCGImage:cgImage];
CGImageRelease(cgImage);
CGDataProviderRelease(provider);
CFRelease(data);
As I understood it the UIImage now owns the CGImage and I shouldn't have to release it.
So is there anyway to ensure that the UIImage is freed when I updated the UIImageView with a new image?
Ok so I finally figured out the problem.
As I said I was using some background thread to perform the image update, the solution was to add it as a autorelease pool as following:
#autoreleasepool {
[self.delegate performSelectorOnMainThread:#selector(updateImage:) withObject:[self fetchImage] waitUntilDone:NO];
}
Related
I develop an application that grabs images from iPhone rear camera. These images are then processed asynchronously.
So I am using AVFoundation functions in Obj-C. My problem is that my app is crashing because of memory issue when capturing images.
Here is the code that I use in the captureOutput callback :
- (void)captureOutput:(AVCaptureOutput *)output didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection
{
connection.videoOrientation = AVCaptureVideoOrientationLandscapeLeft;
CVPixelBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
CIImage* ciimage = [[CIImage alloc] initWithCVPixelBuffer:imageBuffer];
CIContext* context = [CIContext contextWithOptions:nil];
CGImage* cgImage = [context createCGImage:ciimage fromRect:[ciimage extent]];
#synchronized(self) {
UIImage* image = [[UIImage alloc] initWithCGImage:cgImage];
self.uiimageBuffer = image;
}
CGImageRelease(cgImage);
}
As I need to asynchronously process the image grabbed elsewhere in the application, I introduced a buffer called uiimageBuffer. This buffer is updated everytime captureOutput is called, like written right below :
UIImage* image = [[UIImage alloc] initWithCGImage:cgImage];
self.uiimageBuffer = image;
But the allocation of the UIImage leads to memory issue very very quickly (few seconds).
So my question is : how could I update my buffer without allocating new UIImage at every calls of captureOutput ?
PS : the same piece of code written in Swift 4 doesn't lead to memory issue.
Thank you
How about #autoreleasepool? This helped me several times in both captureOutput and requestMediaDataWhenReady.
https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmAutoreleasePools.html
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?
Here is my method:
CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(aSampleBuffer);
CIImage *ciImage = [CIImage imageWithCVPixelBuffer:imageBuffer];
CIContext *context = [CIContext contextWithOptions:nil];
CGImageRef myImage = [context
createCGImage:ciImage
fromRect:CGRectMake(0, 0,
CVPixelBufferGetWidth(imageBuffer),
CVPixelBufferGetHeight(imageBuffer))];
return [UIImage imageWithCGImage:myImage];
But last line show me is Potential leak of an object stored into 'myimage', and the line of myImage is Method returns a Core Foundation object with a +1 retain count. But my application is ARC enabled, so I can't release something. How can I fix it? Thanks.
My application is ARC enabled, so I can't release something
Wrong. ARC prevents you from sending the release message to Objective-C objects, since it manages their memory for you.
However you still have to manually manage the memory in any other case (i.e. C structures). You can - and must - use retain/release functions on such structures whenever appropriate.
In this case you have to manually call CGImageRelease on myImage, balancing the retain count, by doing (as proposed by H2CO3)
UIImage *retVal = [UIImage imageWithCGImage:myImage];
CGImageRelease(myImage);
return retVal;
UIImage *retVal = [UIImage imageWithCGImage:myImage];
CGImageRelease(myImage);
return retVal;
I have a problem with memory in my app.
When I start it, the Documents and Data of my app is 212KB
After I push the ViewControler A, I load in a TableView a lote of images async from Facebook:
UIButton *friendPhoto = [UIButton buttonWithType:UIButtonTypeCustom];
[friendPhoto addTarget:self
action:#selector(friendPhotoWasClicked:)
forControlEvents:UIControlEventTouchUpInside];
[friendPhoto setFrame:CGRectMake(x, photoMargeY, photoSize, photoSize)];
// GET IMAGE ASYNC
NSURL *imageURL = [NSURL URLWithString:faceURL];
[friendPhoto setImage:[UIImage imageNamed:#"default"]
forState:UIControlStateNormal];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
dispatch_async(queue, ^{
NSData *data = [NSData dataWithContentsOfURL:imageURL];
UIImage *image = [UIImage imageWithData:data];
dispatch_async(dispatch_get_main_queue(), ^{
[friendPhoto setImage:image
forState:UIControlStateNormal];
});
});
//}
[cell.contentView addSubview:friendPhoto];
And work perfectly! At this point, the Documents and Data 10MB
But after pop the ViewController and see in NSLog that call dealloc for ViewController, my app still with 10MB. I'm not make cache of the images.
Why still with 10MB? How can I resolve that?
Inspecting all retains/releases on the data and images might help.
If you need to see where retains, releases and autoreleases occur for an object use instruments:
Run in instruments, in Allocations set "Record reference counts" on on (you have to stop recording to set the option). Cause the problem code to run, stop recording, search for there ivar of interest, drill down and you will be able to see where all retains, releases and autoreleases occurred.
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.