Asynchronous Loading of Image - Crashing If Dealloced - iphone

I have a UIViewController presented by a navigation controller. This UIViewController loads an image asynchronously as follows :
[self performSelectorInBackground:#selector(downloadData) withObject:nil];
- (void)downloadData {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
LocationDetails * details = [[LondonDatabase database] locationDetails : UniqueID];
NSData * imageData = [NSData dataWithContentsOfURL : [NSURL URLWithString : [details image]]];
picture = [[UIImage alloc ]initWithData:imageData];
[self performSelectorOnMainThread : #selector(updateUI) withObject : nil waitUntilDone : NO];
[pool release];
}
The problem is that if this view controller is popped off the stack while the above method is executing the app crashes with the error :
bool _WebTryThreadLock(bool), 0x61b3950: 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...
Can anyone help ?
Thanks,
Martin

Use an NSThread and keep a handle to that in the view controller object. When the view controller dealloc's, cancel the thread (assuming it has not completed). In downloadData, check that the thread isn't cancelled before trying to access elements of the view controller. That is to say, if the thread has been cancelled, just return. I had a similar issue and this is how I solved it.
Here's the code (which was loading an image into a custom table cell):
-(void)displayImage:(id)context
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
UIImage *image = (UIImage *)context;
[spinner stopAnimating];
brandImage.image = image;
[brandImage setNeedsDisplay];
[pool release];
}
-(void)fetchImage:(id)context
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSString *url = (NSString *)context;
if ([[NSThread currentThread] isCancelled])
return;
UIImage *image = [ArtCache imageForURL:url];
if (![[NSThread currentThread] isCancelled]) {
[self performSelectorOnMainThread:#selector(displayImage:) withObject:image waitUntilDone:NO];
}
[pool release];
}
-(void)showBrandImageURL:(NSString *)url
{
[spinner startAnimating];
brandImage.image = nil;
if (imageLoadThread) {
[imageLoadThread cancel];
[imageLoadThread release];
}
imageLoadThread = [[NSThread alloc] initWithTarget:self selector:#selector(fetchImage:) object:url];
[imageLoadThread setThreadPriority:0.8];
[imageLoadThread start];
}
These were 3 methods in the custom table cell class. The last one was the one called from cellForRowAtIndexPath:. The other two support the one that is called.

Related

Can we thread the web-service and insert in in custom table cell?

The thing is i have to make Custom cell of UITable, i have to call large webservice with many data which will take longer tym to load it once. So i want to apply threading in it. now my que is. can we at run -time insert value one by one in custom cell , as user will see data comming one by one at run time..?? if yes how we can do that.
yes you can ... try this
- (void)viewDidLoad
{
myApp = (myAppDelegate *) [[UIApplication sharedApplication] delegate];
[self performSelectorInBackground:#selector(startParsing) withObject:nil];
[super viewDidLoad];
}
- (void) startParsing
{
[self performSelectorOnMainThread:#selector(startIndicator) withObject:nil waitUntilDone:NO];
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSURL *url = [[NSURL alloc] initWithString:#"http://abc.com"];
NSData *data = [NSData dataWithContentsOfURL:url];
// 2 -- parsing
parser = [[VolMyParser alloc] init];
[parser parseXML:data];
[data release];
//[parser print];
[self performSelectorOnMainThread:#selector(updateTable) withObject:nil waitUntilDone:NO];
[pool release];
}
- (void) startIndicator
{
av.hidesWhenStopped = YES;
[av startAnimating];
}
- (void) updateTable
{
[av stopAnimating];
[myTable reloadData];
}
Hope its gives you an Idea...

stopping a photo from a new thread to load

I'm loading images with this class. I need help on how to stop them from loading when dealloc is called. This class is extending UIImageView. Also any other advice for designing the class is appreciated, i'll want for example to dispatch the loaded bytes.
#implementation RCPhoto
- (id)initWithFrame:(CGRect)frame delegate:(id)d {
if ((self = [super initWithFrame:frame])) {
// Initialization code
delegate = d;
self.contentMode = UIViewContentModeScaleAspectFit;
}
return self;
}
- (void)dealloc {
[super dealloc];
}
#pragma mark Load photo
- (void)loadImage:(NSString *)path {
// Create a new thread for loading the image
[NSThread detachNewThreadSelector:#selector(load:)
toTarget:self
withObject:path];
}
- (void)load:(NSString *)path {
// You must create a autorelease pool for all secondary threads.
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
//NSLog(#"RCPhoto loadImage into a new thread: %#", path);
NSURL *url = [NSURL URLWithString: path];
NSData *data = [NSData dataWithContentsOfURL: url];
UIImage *image = [[UIImage alloc] initWithData: data];
[self performSelectorOnMainThread: #selector(performCompleteEvent:)
withObject: image
waitUntilDone: NO];
[pool release];
}
- (void)performCompleteEvent:(UIImage *)image {
//NSLog(#"RCPhoto finish loading");
[self setImage:image];
self.opaque = YES;
if ([delegate respondsToSelector:#selector(onPhotoComplete)]) {
[delegate performSelector:#selector(onPhotoComplete)];
}
}
#end
dataWithContentsOfURL: is designed to block until it times out or finishes receiving a complete response. You seem interested in interrupting the background thread when its associated view is deallocated. These facts are fundamentally at odds.
If you want to terminate the request as quickly as possible when your object is going away, this can be done by making an asynchronous request with NSURLConnection and canceling it in your dealloc method. If it isn't canceled before the response is received, you'll get a callback to connectionDidFinishLoading: on your delegate, at which point you can reconstitute your UIImage from the received data.
Final code:
- (void)loadImage:(NSString *)path {
imageData = [[NSMutableData data] retain];
NSURL *url = [NSURL URLWithString: path];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:YES];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)conn {
UIImage *image = [[UIImage alloc] initWithData:imageData];
[self setImage:image];
self.opaque = YES;// explicitly opaque for performance
[image release];
}

Image view not updating on NSThread

I'm trying to make a test app which measures the performance for threads. However, I'm trying to set an image to imageview on a thread and it's not updating. In fact, the methods setImage1 and setImage2 aren't even getting called. Any ideas?
Look at the code for details
#import "UntitledAppDelegate.h"
#import <QuartzCore/QuartzCore.h>
#implementation UntitledAppDelegate
#synthesize window, imageView1, imageView2, label1, label2, label0;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[window makeKeyAndVisible];
return YES;
}
-(IBAction) startSeparate:(id) sender
{
imageView1.image = imageView2.image = nil;
double prev;
start = prev = CACurrentMediaTime();
[self setImage1];
label1.text = [NSString stringWithFormat:#"%.10g", CACurrentMediaTime() - prev];
prev = CACurrentMediaTime();
[self setImage2];
label2.text = [NSString stringWithFormat:#"%.10g", CACurrentMediaTime() - prev];
}
-(IBAction) startThreaded:(id) sender
{
imageView1.image = imageView2.image = nil;
double prev;
NSThread *t1 = [[NSThread alloc] init];
[t1 start];
NSThread *t2 = [[NSThread alloc] init];
[t2 start];
start = prev = CACurrentMediaTime();
//This doesn't work
//[self performSelector:#selector(setImage1) onThread:t1 withObject:nil waitUntilDone:NO];
//But this does
[self performSelectorInBackground:#selector(setImage1) withObject:nil];
label1.text = [NSString stringWithFormat:#"%.10g", CACurrentMediaTime() - prev];
prev = CACurrentMediaTime();
//This doesn't work
//[self performSelector:#selector(setImage2) onThread:t2 withObject:nil waitUntilDone:NO];
//But this does
[self performSelectorInBackground:#selector(setImage2) withObject:nil];
label2.text = [NSString stringWithFormat:#"%.10g", CACurrentMediaTime() - prev];
}
-(void) setImage1
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
UIImage *image1 = [UIImage imageNamed:#"bird.png"];
imageView1.image = image1;
[self pictureDone];
[pool drain];
}
-(void) setImage2
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
UIImage *image2 = [UIImage imageNamed:#"bird.png"];
imageView2.image = image2;
[self pictureDone];
[pool drain];
}
-(void) pictureDone
{
done++;
NSLog(#"%i", done);
if (done == 2) {
label0.text = [NSString stringWithFormat:#"%.10g", CACurrentMediaTime() - start];
done = 0;
}
}
#end
Never change anything in a currently displayed UIView from a background thread. There are a million different synchronization problems that you can encounter. Download the image in the background thread, and then call another method on the main thread (from the background thread) to actually update the UIImageView
performSelectorOnMainThread:...
As for the problem with the method not running, try this instead:
[self performSelectorInBackground:#selector(setImage2:) withObject:nil];
(adding a colon after setImage2).
Also, I don't think those two NSThread objects (t1 and t2) are necessary, and they are also probably creating a memory leak.
Technically, NSThread itself do not create run loop automatically, then the performSelector:onThread won't work if you just create thread that way.

How can I disable a button while NSXMLParser runs, then enable it when it completes?

I have a viewController which imports XMLParser.h as the class xmlParser
I'm passing an NSURL object in my viewController to the xmlParser class with the getXML method below
goButton is the button I tap to call the getXML method below. I disable the button which I tapped to trigger the getXML method, but I'm not sure where to put the code to enable it again once the xmlParser has finished parsing the returned XML.
- (IBAction) getXML {
goButton.enabled = NO;
// allocate and initialize the xmlParser
xmlParser = [[XMLParser alloc] init];
// then generate the URL we are going to pass to it and call the fetchXML method passing the URL.
NSURL *xmlurl = [[NSURL alloc] initWithString:#"http://www.mysite.com/myfile.xml"];
[xmlParser fetchXMLFromURL:xmlurl];
// release objects
[xmlurl release];
[xmlParser release];
}
As per #Squeegy recommendation, I modified my code.
- (IBAction) getXML {
goButton.enabled = NO;
xmlParser = [[XMLParser alloc] init];
[self performSelectorInBackground:#selector(parseInBackground:) withObject:xmlParser];
}
- (void)parseInBackground:(XMLParser*)parser {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSURL *xmlurl = [[NSURL alloc] initWithString:#"http://www.mysite.com/myfile.xml"];
[parser fetchXMLFromURL:xmlurl];
[self performSelectorOnMainThread:#selector(didFinishXMLParsing:) withObject:parser];
[xmlurl release];
[pool drain];
}
- (void)didFinishXMLParsing:(NSXMLParser*)parser {
goButton.enabled = YES;
}
Looks to be working until it gets to the line
[self performSelectorOnMainThread:#selector(didFinishXMLParsing:) withObject:parser];
The compiler complains as follows:
2010-02-17 00:22:20.574 XMLApp[2443:521b] *** -[viewController performSelectorOnMainThread:withObject:]: unrecognized selector sent to instance 0x1285a0
2010-02-17 00:22:20.578 XMLApp[2443:521b] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[viewController performSelectorOnMainThread:withObject:]: unrecognized selector sent to instance 0x1285a0'
2010-02-17 00:22:20.583 XMLApp[2443:521b] Stack: (
861696817,
860329709,
861700631,
861203093,
861166272,
18715,
846004025,
845672609,
848189713
)
- (IBAction)getXML {
goButton.enabled = NO;
xmlParser = [[XMLParser alloc] init];
NSURL *xmlurl = [[NSURL alloc] initWithString:#"http://www.mysite.com/myfile.xml"];
[xmlParser fetchXMLFromURL:xmlurl];
[self performSelectorInBackground:#selector(parseInBackground) withObject:xmlParser];
[xmlurl release];
[xmlParser release];
}
- (void)parseInBackground:(NSXMLParser*)parser {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[parser parse];
[self performSelectorOnMainThread:#selector(didFinishXMLParsing:)
withObject:parser
waitUntilDone:NO];
[pool drain];
}
- (void)didFinishXMLParsing:(NSXMLParser*)parser {
goButton.enabled = YES;
}
The trick is to do the processing on a background thread, which allows the UI to do stuff. When parsing is done, you have to make any UI changes back on the main thread.
When the parser finishes parsing, it will call it's delegate's:
- (void)parserDidEndDocument:(NSXMLParser *)parser
In that method, you can re-enable the button. You should probably do so with a performSelectorInMainThread call, since it involves changing a view.

Painting a UIView from a background NSThread?

I am developing GL paint application.
To paint any object, I am using UIView that implemented.
Starting paint method :
- (void)viewDidLoad {
....
[NSThread detachNewThreadSelector:#selector(paintingObjects)
toTarget:self
withObject:nil];
}
- (void)paintingObjects {
while(1) {
[NSThread sleepForTimeInterval:2];
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
ImplementedView *tmp = [self view];
[tmp draw];
[pool drain];
}
}
But it is not working (doesn't paint object).
What is wrong here ?
Please help me guys.
Thanks in advance.
All user-interface classes are not thread-safe and must be called from the main thread:
- (void)paintingObjects {
while(1) {
[NSThread sleepForTimeInterval:2];
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
ImplementedView *tmp = [self view];
[tmp performSelectorOnMainThread:#selector(draw) withObject:nil waitUntilDone:YES];
[pool drain];
}
}
In this case, you would likely be better off using NSTimer.