Become a MacRumors Supporter for $50/year with no ads, ability to filter front page stories, and private forums.

Sean7512

macrumors 6502a
Original poster
Jun 8, 2005
854
37
Hey everyone,

I am working on my app and seem to have hit some mysterious run-time errors once I introduced threading. Below is my code before introducing threads, and it works 100% fine...

Code:
- (IBAction)downloadButtonPressed:(id)sender {
	statusLabel.text = @"Getting new data...";

	ServerManager *sm = [[ServerManager alloc] init];
		
	NSArray *temp = [sm getDataAtLocation:currentLocation];
	[userData addObjectsFromArray:temp];
		
	for(int i=0; i<[userData count]; i++) {
		Car *aCar = (Car *) [userData objectAtIndex:i];
				
		NSString *st = [[NSString alloc] initWithFormat:@"Year: %@", aCar.Year];
		[aCar release];

                // Update UITextView log
		results.text = [results.text stringByAppendingString:st];
		[st release];
	}

        // Scroll to last line
        [results scrollRangeToVisible:NSMakeRange([results.text length], 0)]
		
	[sm release];
	
	statusLabel.text = @"Download Finished.";
}

Now the code above works fine, however there are two problems with it. First, this is all on the UI thread, which may take a while to complete and that is obviously not good. Also, I need to have the code inside a loop so it continually gets new data "pushed" to the UITextView. This would continue until the user presses a "Stop" button. So, With this in mind, I re-wrote my code to what is below:

Code:
- (IBAction)stopButtonPressed:(id)sender {
	keepScanning = false;
	statusLabel.text = @"Stopped by user";
}

- (void) startDownload:(NSString*)s {/
    NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
	
	while (keepDownloading) {
		ServerManager *sm = [[ServerManager alloc] init];
		
                NSArray *newData = nil;
	        @synchronized (currentLocation) {
			newData = [sm getDataAtLocation:currentLocation];
		}
	       @synchronized (userData) {
			[userData addObjectsFromArray:newData];
		}
		
		//Use callback to update GUI
		[self performSelectorOnMainThread:@selector(downloadComplete:) withObject:newData waitUntilDone:NO];
		
		[newData retain];
		
		[NSThread sleepForTimeInterval:1];
	}
	
	// release thread's pool
	[pool release];
}

//callback
- (void) downloadComplete: (NSArray *)newData {
	
	for(int i=0; i<[newData count]; i++) {
		Car *aCar = (Car *) [newData objectAtIndex:i];
				
		NSString *st = [[NSString alloc] initWithFormat:@"Year: %@", aCar.Year];
		[aCar release];

                @synchronized (results) {
                       // Update UITextView log
		       results.text = [results.text stringByAppendingString:st];
		       [st release];

                       // Scroll to last line
                       [results scrollRangeToVisible:NSMakeRange([results.text length], 0)];
                }
	}
}

- (IBAction) downloadButtonPressed:(id)sender {
	// If already downloading, do nothing
	if (!keepDownloading) {
		keepDownloading = true;
		statusLabel.text = @"Getting new data...";
		
		//Start our threads -- this class method creates a new NSThread object to execute
		//the selector of our choice.
		[NSThread detachNewThreadSelector:@selector(startDownload:) toTarget:self withObject:nil];
	}
}

So now the code above is multi-threaded and does exactly what I need it to. However, its never that easy...Sometimes the code above works 100% flawless, other times it crashes upon pressing "Download" or other times it will crash in the middle of updating the UITextView, etc. The error the console is showing is "Invalid selector sent to object at <some memory address>." It used to crash everytime, but adding in those few synchronized blocks has helped, so I am suspecting that there is some concurrency issue that I am just overlooking. Anyone have any ideas, my eyes are burning and I would appreciate it if another pair of eyes looked at it real quick.

Thanks!
-Sean
 

Sean7512

macrumors 6502a
Original poster
Jun 8, 2005
854
37
I believed that I've narrowed it down to the GUI update method. If I comment out the following line, it works fine:

Code:
[self performSelectorOnMainThread:@selector(downloadComplete:) withObject:newData waitUntilDone:NO];

This is obviously not a good solution, as the app needs to have visual feedback during download. Does anyone see anything that jumps out at them in my code?

Thanks,
Sean
 

PhoneyDeveloper

macrumors 68040
Sep 2, 2008
3,114
93
NSURLConnection does threaded downloads. You are basically duplicating its functionality. You're better off using that class.

Also, if you must use your own threads you are better off using NSOperationQueue. It's simpler to use.
 

Sean7512

macrumors 6502a
Original poster
Jun 8, 2005
854
37
NSURLConnection does threaded downloads. You are basically duplicating its functionality. You're better off using that class.

Also, if you must use your own threads you are better off using NSOperationQueue. It's simpler to use.

Once the information is downloaded, it needs handled in a special way, so either way, I'll need to manually create my own thread. :(

I'll look into NSOperationQueue, although I thought the NSThread was simple, but anything to rid my runtime exception!

Finally, this is the exact error I am receiving:

Code:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[NSCFNumber coordinate]: unrecognized selector sent to instance 0x103a20'
 

PhoneyDeveloper

macrumors 68040
Sep 2, 2008
3,114
93
I don't see your code using the method 'coordinate' in the code you posted and it's not a UIKit method. I guess I'll assume it's from your code but not in the code you posted.

The usual cause of this run time exception is that something hasn't been retained correctly. In your case it would be an object of whatever class implements the coordinate method.

Also, don't cross-post like this.
 
Register on MacRumors! This sidebar will go away, and you'll see fewer ads.