I am loading images to UITableViewCell using the following function. But when I run my app with Debugger its getting crashed at [pool release] whenever I scroll my UITableView. What might I do to solve this? Thanks in advance.
- (void) loadImage{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
imageURL = [imageURL stringByAddingPercentEscapesUsingEncoding:NSASCIIStringEncoding];
//NSLog(#"img url cell**** %#",imageURL);
self.image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:imageURL]]];
//self.image = [NSData dataWithContentsOfURL:[NSURL URLWithString:imageURL]];
[pool release];
}
By the fact that you are using a NSAutoreleasePool I guess that load image is running in a thread that is not the main thread. Is this Correct? If that is the case you are making a UIKit invocation (self.image = ...) in this non-main thread and this is a possible source of the crash you are experiencing. All UIKit updates must be made in the main thread, since UIKit is not thread safe. Try replacing:
self.image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:imageURL]]];
by
[self performSelectorOnMainThread:#selector(setImage:) withObject:[UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:imageURL]]] waitUntilDone:YES];
Notice I'm guessing the name of the setter is setImage:, you may need to correct if the setter selector has a different name that.
First of all, it is recommended to use [pool drain] instead of [pool release].
But I see another potential issue here: according to your code, you defined initially imageURL outside of your newly created autorelease pool (I don't see any alloc/init for imageURL, where is coming from? is it a table cell instance?) then you reassign imageURL with a newly allocated and autoreleased string: [imageURL stringByAddingPercentEscapesUsingEncoding:...]. Finally when you release the pool the new imageURL is release, so at the end what you had is a leak of the previously allocated imageURL. I can expect at this point that when exiting from the inner autorelease pool the run loop autorelease tries to release imageURL again (for example, but I need to see your code to fully understand) which has been deallocated.
Can you try to assign the result of stringByAddingPercentEscapesUsingEncoding: to a different name (e.g. myImageURL)?
Quite a few things wrong here.
Firstly, your need of an autorelease pool quite clearly suggests that you're not doing this work on the main thread. UIKit is not thread-safe. Any UI work needs to be done on the main thread or undefined behaviour will occur, such as crashes.
Secondly, you're using what looks like a synchronous URL download of an image. Correct me if you're loading it from a file URL on disk. A synchronous image download is probably the reason why you've moved this into a separate thread, because it was blocking your UI right? One of the first things you should learn about network access it to never use a synchronous API on the main thread. Have you considered looking at the NSURLConnection class in order to do an asynchronous download?
Thirdly, this needs to be re-architected so that the image download isn't directly linked to the display of the cell.
Displaying the table cell should be done on the main thread with the rest of the UI. When the cell is displayed, it should make a call to download the image asynchronously. The image download should be cached, and the cell should be informed via a delegate callback when the image download is complete. It should then display the image.
You can wrap these kind of things up in NSOperations and use NSOperationQueue to manage them. Please, look at the documentation for these classes and checkout the example code for them to learn how to use them.
Related
I am loading Facebook images. I am trying to use the code below to return an image so that I can save it to the record of a 'Friend' object using Core Data. It isn't working.. nothing gets returned. Is there any way to load the data in the background and then create the image on the main queue, and return it? I need my image created on the main queue because I want to save it to my object with my app delegate's managed object context.
+ (UIImage *)imageForObject:(NSString *)objectID {
NSString *url = [[NSString alloc] initWithFormat:#"https://graph.facebook.com/%#/picture?width=%d&height=%d",objectID,IMAGE_MAXWIDTH,IMAGE_MAXHEIGHT];
__block UIImage *image;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:url]];
dispatch_async(dispatch_get_main_queue(), ^{
image = [[UIImage alloc] initWithData:data];
});
});
return image;
}
Sure, it's possible to load images in the background, but not like this. This method is designed so that it's really impossible for it to not return nil. When you call dispatch_async, it returns immediately and finishes its work asynchronously. That's why it has "async" right in its name. You code reaches return image; before you've loaded the image.
You're on the right track, though. The usual approach is to dispatch back to the main queue when the background work is finished. But when doing that, it's impossible for a method like yours to return the image. If it did, the main queue would have to wait around until the image was loaded, which defeats the whole purpose of running on a background queue.
What you should do instead is:
In your dispatch_async back to the main queue, do whatever work you need to do with the image-- show it on the screen, or write it to a file, or whatever it is you need to do with the image.
Make your method return void, since it can't return the image directly to the caller. Or if you like, make it return BOOL to indicate whether the image load was started. But it can't return the UIImage unless you make the main queue wait on the image-- which as I mentioned, defeats the whole purpose of using background queues.
In my app, I load image from the URL:
-(void)loadImages
{
...
image1 = [UIImage imageWithData:[NSData dataWithContentsOfURL:imgUrl1]];
}
In order to avoid blocking the main thread until the download has completed I call this method in
-viewDidAppear using GCD:
dispatch_async( dispatch_get_global_queue(0,0), ^{
[self loadImages];
});
However, when I open my view controller with the imageView at the first time, the imageView is empty (even if I wait for a long time) but after I open this view controller again and image appears and everything is good to go.
Where is my mistake ?
Sorry, new to multithreading :)
EDIT:
I also forgot to mention, that I use the image in the tableView when I get it:
cell.imageView.image = image1;
This may not be the answer that you were looking for, but that's not the recommended way to load URLs. You should use the URL loading classes available such as NSURLRequest and NSURLConnection.
Try this:
NSURLRequest *imageRequest = [[NSURLRequest alloc] initWithURL:imageURL];
[NSURLConnection sendAsynchronousRequest:imageRequest queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if (!error) {
UIImage *image = [[UIImage alloc] initWithData:data];
[imageView setImage:image];
}
}];
UIElements in iOS should always be updated via main thread.
What you could do is:-
__block NSData *data;
dispatch_queue_t myQueue = dispatch_queue_create("com.appName", NULL);
dispatch_async(myQueue, ^{
data = [NSData dataWithContentsOfURL:imgUrl1];
dispatch_async(dispatch_get_main_queue(), ^(void) {
cell.imageView.image = [UIImage imageWithData = data];
});
});
or Else you there is a better way of fetching images from URL by using AFNetworking. It is faster and easier.
You just have to write one line of code:-
[cell.imageView setImageWithURL:imgUrl1];
You have many problems here:
You can not start networking requests on an thread not running a runloop.
You can not update your UI from a thread other than the main thread.
[NSData dataWithContentsOfURL:imgUrl1] is not a safe way to load an external resource, even on a different thread (but especially on the main thread).
Any time you dispatch to a different thread, you run the risk that your table cell has been recycled and is no longer showing the data you think it is. (It's still the same cell instance, but is now showing some other row's data.)
What you should be doing:
Start your network operation using asynchronous calls on the main thread. (You can use another thread or queue if you want, but you need to make sure it's running a runloop.)
From your delegate messages, dispatch your image decoding on a different thread.
After the image is decoded, dispatch back to the main thread to update it.
Before actually assigning the image, check that the cell is still being used for the purpose you think.
You can solve the first three problems by using AFNetworking. It wraps the delegate methods and lets you just provide a success and failure block. AFNetworking's AFImageRequestOperation in particular bounces code between queues as I've described. (It even runs its main networking loop in a different thread, which isn't necessary but since it does it well, why not?)
You'll still need to verify the cell's identity.
You need to inform the view that the image needs to be redrawn. Add:
[imageView setNeedsDisplay];
to the end of your loadImages method.
Since you are using it in TableView, add [self.tableView reloadData]; in the end of loadImages method.
Hi every one I have the next issue I'm creating various images like this
UIImage *image = [UIImage imageNamed:_imageName];
and I'm assigning to one UIImageView
[self.imgvImage setImage:image];
The user when tap a button one UIImageView is created and one UIImage is assigning, I have 4 UIImageViews created when new is created the last is removed sending release to the UIImageView like this
[_imgvImage release];
But around than 100 times creating and releasing one memory warring happen but the app dont crash I think this is because the UIImages created are not releasing and there are so much because all the code are clean and creating and releasing are all fine.
How can I remove all the UIIMageView with his UIImage completely from the memory.
Please help, sorry for my English in not good.
The image created using below statement is handled by iOS. this image is stored in a cache so that next time you call this it is simply get returned from the cache.
UIImage *image = [UIImage imageNamed:_imageName];
I think you should not worry about memory warning as long as you are releasing what you are allocating (UIImageView in this case). iOS should clear this cache in case of memory crunch.
If you do not want that caching (e.g. when you are loading one image once only) you can use imageWithContentsOfFile: method of UIImage.
Hi I found the solution the solution was call the
NSAutoreleasePool
in the method when I removed the objects of the view like this:
for(ProductVW *pVW in [_vwProductsContainer subviews]){
NSAutoreleasePool * autoreleasePool = [[NSAutoreleasePool alloc] init];
[pVW removeFromSuperview];
[autoreleasePool release];
}
I have written code that when you hit button, it opens new screen with image on it. On that new screen there is button that dismisses screen, and returns to main screen. And it works fine if i do it like this (no leaks etc...):
img = [UIImage imageNamed: #"Galaxy"];
ImageDisplay *display = [[ImageDisplay alloc] initWithImage:img];
But if i replace this line of code with something like this:
img = [[UIImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:#"Minnesota" ofType:#"png"]];
ImageDisplay *display = [[ImageDisplay alloc] initWithImage:img];
[img release];
It acts as i have memory leak. Every time i open image screen, app takes more and more memory. But all deallocs are called, even [img retainCount] shows 1 before final release. Is there possibility that there is a bug here, because i cant find whats wrong?
EDIT:
Here is dealloc method for ImageDisplay, and this method gets called:
-(void) dealloc {
[img release];
[super dealloc];
}
Your ImageDisplay *display is retaining the image. As it should be. When you release that, it should release all its retained entities. In the code you've shown, you're not releasing it. The typical use would be to tell the containing view controller to display it modally or something (or push it onto a navigation controller) and release it, leaving its retain lifecycle in the hands of whatever view controller is now managing it. The difference is, in your first code sample, *img is autoreleased, and will release itself when appropriate, and in the second, it's not.
ARC would save your bacon here, and dramatically simplify your code.
Also you should google the term "static method", because you're working really hard to call static methods as instances of the class of objects, which is like going around your ass to get to your elbow.
ALSO, stop looking at retainCount. All sorts of things might retain your objects under the hood of the framework. Using retainCount as part of your debugging strategy is a one way ticket to confusionville.
Try using this one instead, you dont need to allocate and release:
[UIImage imageWithContentsOfFile:(NSString *)name]
Note that since imageNamed: is a class method not an instance method, you use it like this:
UIImage *myImage = [UIImage imageNamed:#"pony.png"];
Your posted code using initWithContentsOfFile looks correct, so the leak must be somewhere in your ImageDisplay class.
I have a UIViewController and in that controller, i am fetching an image from a URL source. The image is fetched in a separate thread after which the user-interface is updated on the main thread. This controller is displayed as a page in a UIScrollView parent which is implemented to release controllers that are not in view anymore.
When the thread finishes fetching content before the UIViewController is released, everything works fine - but when the user scrolls to another page before the thread finishes, the controller is released and the only handle to the controller is owned by the thread making releaseCount of the controller equals to 1. Now, as soon as the thread drains NSAutoreleasePool, the controller gets releases because the releaseCount becomes 0. At this point, my application crashes and i get the following error message:
bool _WebTryThreadLock(bool), 0x4d99c60: Tried to obtain the web lock from a thread other than the main thread or the web thread. This may be a result of calling to UIKit from a secondary thread. Crashing now...
The backtrace reveals that the application crashed on the call to [super dealloc] and it makes total sense because the dealloc function must have been triggered by the thread when the pool was drained. My question is, how i can overcome this error and release the controller without leaking memory?
One solution that i tried was to call [self retain] before the pool is drained so that retainCount doesn't fall to zero and then using the following code to release controller in the main thread:
[self performSelectorOnMainThread:#selector(autorelease)
withObject:nil waitUntilDone:NO];
Unfortunately, this did not work out. Below is the function that is executed on a thread:
- (void)thread_fetchContent {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSURL *imgURL = [NSURL URLWithString:#"http://www.domain.com/image.png"];
// UIImage *imgHotspot is declared as private - The image is retained
// here and released as soon as it is assigned to UIImageView
imgHotspot = [[[UIImage alloc] initWithData:
[NSData dataWithContentsOfURL: imgURL]] retain];
if ([self retainCount] == 1) {
[self retain]; // increment retain count ~ workaround
[pool drain]; // drain pool
// this doesn't work - i get the same error
[self performSelectorOnMainThread:#selector(autorelease)
withObject:nil waitUntilDone:NO];
}
else {
// show fetched image on the main thread - this works fine!
[self performSelectorOnMainThread:#selector(showImage)
withObject:nil waitUntilDone:NO];
[pool drain];
}
}
Please help! Thank you in advance.
Yeah it can be really daunting to try and keep threaded stuff in sync.
The use case you describe sounds perfect for an NSOperation.
By using this approach you can have an NSOperationQueue as an ivar in the controller and release this in your controllers dealloc method.
The benefits are many, when the controllers view is visible in the scrollView it (viewWillAppear or loadView) starts retrieving the image using an NSOperation added to an NSOperationQueue, if the user then scrolls away before the operation is done and the NSOperationQueue is released, it will take care of sending a cancel message to all operations and in general close everything down in an orderly fashion.
If this is a central component in your app, which I guess it is since you put thought into releasing things that are "of screen", I would recommend having your controller display a "dummy image" in the loadVIew method and then start a fetch operation in the viewDidLoad. You could subclass NSOperation so that you just send it the URL and let it do its thing.
I did something similar some weeks ago where I had to continuously start threaded operations, but with a large chance the the user would do something that caused these to get canceled. That functionality is "build" into the NSOperation.
NSOperation question