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

bauhaus9mf

macrumors newbie
Original poster
Jun 9, 2009
5
0
I'm trying to update a UIProgressView (that was created in IB) as I get chunks of data from an Asynch download using NSURLConnection. Without putting the async download into a background thread, the progress view update is all or nothing--that is, you don't see any activity in the progress view UNTIL the download is finished.

So I have the 4 key methods needed to do an asynch download: didReceiveData, didReceiveResponse, didFailWithError, and connectionDidFinishLoading. And I've got these 4 methods in the implementation of my ViewController class. I've tried to use "performSelectorInBackground" method to try to put such activity into a background thread but when I do (as the last line of code in my "viewDidLoad()" method), it just goes into la-la land and never executes any of the 4 callbacks mentioned above. I have a matching "performSelectorOnMainThread" call in the body of my "didReceiveData" method that invokes a simple method to update the UIProgressView attribute after calculating how much data was pulled down in the current chunk. When NOT using the "performSelectorInBackground" and "performSelectorOnMainThread" the 4 callback methods get called just fine and the download works. Some questions:

1. The method signature that I pass as the "selector" to the "performSelectorInBackground" is "-(void) startIt". Should this be "-(SEL) startIt" ? I get no complaints from the compiler using (void). Specifically, the call I use is:

Code:
[self performSelectorInBackground:@selector(startIt) withObject:nil];

and the details of my "startIt" method are:

Code:
- (void) startIt {
	NSAutoreleasePool	 *autoreleasepool = [[NSAutoreleasePool alloc] init];
	downloadURL = [[NSURL URLWithString:@"http://www.gutenberg.org/files/135/135.txt"] retain];
	data = [[NSMutableData data] retain];
	myConnection = [[[NSURLConnection alloc] initWithRequest: [NSURLRequest requestWithURL: downloadURL] delegate: self ] retain];
	[autoreleasepool release];
}

2. Do I need to do any kind of thread creation or setup in my "startIt" method.

3. Is there any more direct way of connecting a UIProgressView to asynch NSURLConnection activity?

4. What XCode tools are there for monitoring thread activity in specific and the Cocoa framework in general that are recommended. I'd like to know where my app goes when it just sits there in "la-la land".
 

Luke Redpath

macrumors 6502a
Nov 9, 2007
733
6
Colchester, UK
Because the URL loading system is asynchronous and takes care of the threading for you, the performSelectorInBackground call is redundant and will probably cause problems. Just call startIt normally (and no need for the extra auto release pool).

Any updates to your UI from your async delegate callbacks should be performed on the main thread (as should all updates).
 

bauhaus9mf

macrumors newbie
Original poster
Jun 9, 2009
5
0
Tried that...that's how I started...

Just calling "startIt" was where I started with this issue. Here is my "didReceiveData" method:

Code:
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)mdata {
	[data appendData: mdata];
	cumDataReceived += [mdata length];
	//[self performSelectorOnMainThread:@selector(updateProgressView) withObject:nil waitUntilDone:NO];
	[self updateProgressView];
}

If I put a breakpoint in the middle of the "didReceiveData" method, it stops each time the NSURLConnection thread invokes my "didReceiveData" callback and it then invokes the following method...

Code:
-(void) updateProgressView {
	if (bookLen > 0) 
		progressBar.progress = cumDataReceived / bookLen;
}

(where progressBar is of type UIProgressView)

But the UI never updates until after it is all over! Am I missing some crucial step?

BTW, thanks for the quick reply...
 

bauhaus9mf

macrumors newbie
Original poster
Jun 9, 2009
5
0
I am on the main thread...

Ok, I modified the "didReceiveData" method to tell me if, when I get called-back from NSUrlConnection, whether or not I am on the main thread:

Code:
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)mdata {
	[data appendData: mdata];
	cumDataReceived += [mdata length];
	//[self performSelectorOnMainThread:@selector(updateProgressView) withObject:nil waitUntilDone:NO];
	if (YES == [NSThread isMainThread])
		NSLog(@"I'm on the main thread and I've received %d bytes so far.",cumDataReceived);
	
	[self updateProgressView];
}

and in the console window, I get messages like the following...

Code:
2009-06-09 13:06:49.920 ASR3[2974:20b] I'm on the main thread and I've received 3316512 bytes so far.
2009-06-09 13:06:49.922 ASR3[2974:20b] I'm on the main thread and I've received 3319144 bytes so far.
2009-06-09 13:06:49.977 ASR3[2974:20b] I'm on the main thread and I've received 3320460 bytes so far.
2009-06-09 13:06:49.978 ASR3[2974:20b] I'm on the main thread and I've received 3321776 bytes so far.
2009-06-09 13:06:49.981 ASR3[2974:20b] I'm on the main thread and I've received 3322623 bytes so far.
2009-06-09 13:06:49.981 ASR3[2974:20b] Finished (http://www.gutenberg.org/files/135/135.txt) 3322623 bytes.

so, sure enough, I AM on the main thread when I try to update the UIProgressView, so NOW what...?
 

dejo

Moderator emeritus
Sep 2, 2004
15,982
452
The Centennial State
Are you still doing this?:
Code:
[self performSelectorInBackground:@selector(startIt) withObject:nil];
or are you doing this?:
Code:
[self startIt];
 

bauhaus9mf

macrumors newbie
Original poster
Jun 9, 2009
5
0
I have gone back to using the regular:

Code:
[self startIt];

Please help, oh demi-god!
 

dejo

Moderator emeritus
Sep 2, 2004
15,982
452
The Centennial State
Then, I believe, based on Luke Redpath's advice:
Code:
[self performSelectorOnMainThread:@selector(updateProgressView) withObject:nil waitUntilDone:NO];
 

bauhaus9mf

macrumors newbie
Original poster
Jun 9, 2009
5
0
NOTHING CHANGED <sigh>

The "didReceiveData" callback is in the SAME class as the "updateProgressView" method--could this be the problem? Commenting out the "[self updateProgressView] in exchange for the recommended

Code:
[self performSelectorOnMainThread:@selector(updateProgressView) withObject:nil waitUntilDone:NO];

changed nothing. And it makes sense to me that nothing would change. If am already running on the main thread, then telling the framework to run my "updateProgressView" method on the main thread has no impact because the method already IS part of a class that is running on the main thread.

Maybe if I post the entire source file someone might spot something silly I am doing...(thanks for hanging in there!)

Here is the .H file (ignore the NSOperationQueue attribute and the timer method plz):

Code:
#import <UIKit/UIKit.h>

@interface ASR3ViewController : UIViewController {
	IBOutlet UIProgressView * progressBar;
	NSOperationQueue *queue;

	NSURLConnection *myConnection;
	NSMutableData *data;
	NSURL *downloadURL;
	
	BOOL executing;
	BOOL finished;
	NSInteger cumDataReceived, bookLen;
	
}

@property (nonatomic,assign) 	IBOutlet UIProgressView * progressBar;


- (void)timedMethod:(NSTimer*)theTimer;

-(void) updateCompletedState;
-(void) updateProgressView;


- (void) startIt;

@end

------------- and here is the implementation file (.M file) ---------------

Code:
#import "ASR3ViewController.h"

@implementation ASR3ViewController


@synthesize progressBar;


- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)mdata {
	[data appendData: mdata];
	cumDataReceived += [mdata length];
	[self performSelectorOnMainThread:@selector(updateProgressView) withObject:nil waitUntilDone:NO];
	//if (YES == [NSThread isMainThread])
	//	NSLog(@"I'm on the main thread and I've received %d bytes so far.",cumDataReceived);
	
	//[self updateProgressView];
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
	NSLog(@"Finished (%@) %d bytes.", [downloadURL absoluteString], [data length]);
	[self updateCompletedState];
}

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
	NSHTTPURLResponse * resp = (NSHTTPURLResponse*) response;
	NSDictionary * headers = [resp allHeaderFields];
	NSString * contentLength = [headers objectForKey:@"Content-Length"];
	bookLen = [contentLength intValue];
}


- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
	NSLog(@"Failed (%@)", [downloadURL absoluteString]);
	[self updateCompletedState];
}

- (void) startIt {
	downloadURL = [[NSURL URLWithString:@"http://www.gutenberg.org/files/135/135.txt"] retain];
	data = [[NSMutableData data] retain];
	myConnection = [[[NSURLConnection alloc] initWithRequest: [NSURLRequest requestWithURL: downloadURL] delegate: self ] retain];
}

- (void)timedMethod:(NSTimer*)theTimer {
}

// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad {
    [super viewDidLoad];
	//[self performSelectorInBackground:@selector(startIt) withObject:nil];
	[self startIt];
}

-(void) updateProgressView {
	if (bookLen > 0) 
		progressBar.progress = cumDataReceived / bookLen;
}


- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning]; // Releases the view if it doesn't have a superview
    // Release anything that's not essential, such as cached data
}

- (void)updateCompletedState {
	[self willChangeValueForKey: @"isExecuting"];
	executing = NO;
	[self didChangeValueForKey: @"isExecuting"];
	
	[self willChangeValueForKey: @"isFinished"];
	finished = YES;
	[self didChangeValueForKey: @"isFinished"];
}

- (void)dealloc {
	[downloadURL release];
	[data release];
	[myConnection release];	
    [super dealloc];
}



@end
 

pranathi

macrumors newbie
Jun 18, 2009
1
0
>>> progressBar.progress = cumDataReceived / bookLen;
You need to cast the result of the division to a float value and assign it. Otherwise, the progress always remains zero causing the progress bar to not update.
 

radshag

macrumors newbie
Dec 31, 2009
2
0
Solution to UIProgressView display problems

Use this:
[self performSelectorInBackground:mad:selector(updateProgressView) withObject:nil];

performSelectorInBackground will run the selector in the background and your UIProgressView will be updated.

All your other code should be the same just change the above line with the line that has the performSelectorInMainThread and you are good to go
:)
 

Luke Redpath

macrumors 6502a
Nov 9, 2007
733
6
Colchester, UK
Use this:
[self performSelectorInBackground:mad:selector(updateProgressView) withObject:nil];

performSelectorInBackground will run the selector in the background and your UIProgressView will be updated.

All your other code should be the same just change the above line with the line that has the performSelectorInMainThread and you are good to go
:)

No; UI updates should never be called on a background thread. UIKit is not thread-safe.
 

radshag

macrumors newbie
Dec 31, 2009
2
0
Please Explain

Why not in a background thread? Please explain.

None of the solutions on this page work. Mine did.

Thanks....

Perhaps I should have the DB Request and SOAP Request running in a seperate thread. That makes more sense....
 

kainjow

Moderator emeritus
Jun 15, 2000
7,958
7
Why not in a background thread? Please explain.

None of the solutions on this page work. Mine did.

Thanks....

Perhaps I should have the DB Request and SOAP Request running in a seperate thread. That makes more sense....

Because that's The Way It Is™. Don't handle any thing from UIKit in a thread other than the main thread.

http://developer.apple.com/mac/libr...aviortoaCocoaProgram/AddingBehaviorCocoa.html
All UIKit objects should be used on the main thread only.

If you're handling HTTP data, use NSURLConnection and it works asynchronously automatically. If you're still noticing a lag when dealing with the data, then you could consider threading it, but you should read up more on it before you do that.
 

troyerrdt

macrumors newbie
Jun 29, 2011
1
0
Confused

I know this thread is old, but I have the same problem: I read everywhere that you should use performSelectorOnMainThread, but the progress view doesn't get updated visually until it is at 100%, but if I use performSelectorInBackground or detachNewThreadSelector, it works, like radshag said. What gives?
 

spenceu41

macrumors newbie
May 18, 2012
1
0
Found a solution for me

It's funny. I ran into the same problem 2 years later. The problem has very little to do with threading but more about the floating value I send to update UIProgressView. For some reason sending the percentage wrapped in NSNumber doesn't do it. I need to multiply 100. Then inside updateProgressView convert that to float and divided by 100.

Here the solution that works for me.

Call startIt like you normally would
Code:
[self startIt];

Then inside connection delegate
Code:
- (void)connection:(NSURLConnection *)connection didSendBodyData:(NSInteger)bytesWritten totalBytesWritten:(NSInteger)totalBytesWritten totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite
{
    float percent = 100*totalBytesWritten/totalBytesExpectedToWrite;
    [self performSelectorOnMainThread:@selector(updateProgressView:) withObject:[NSNumber numberWithFloat:percent] waitUntilDone:NO];

}
- (void) updateProgressView:(NSNumber *)percent
{
    progressBar.progress    = [percent floatValue]/100;
}
 

dejo

Moderator emeritus
Sep 2, 2004
15,982
452
The Centennial State
For some reason sending the percentage wrapped in NSNumber doesn't do it. I need to multiply 100. Then inside updateProgressView convert that to float and divided by 100.

Here the solution that works for me.

Code:
- (void)connection:(NSURLConnection *)connection didSendBodyData:(NSInteger)bytesWritten totalBytesWritten:(NSInteger)totalBytesWritten totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite
{
    float percent = 100*[COLOR="Red"][B]totalBytesWritten/totalBytesExpectedToWrite[/B][/COLOR];
    [self performSelectorOnMainThread:@selector(updateProgressView:) withObject:[NSNumber numberWithFloat:percent] waitUntilDone:NO];

}
- (void) updateProgressView:(NSNumber *)percent
{
    progressBar.progress    = [percent floatValue]/100;
}

I think the reason you feel the need to multiply by 100, is that without it, your division (highlighted in red above) is done using integer mathematics, the result of which is then converted to a float. So, you're losing all the precision in your division. I believe if you use the line below you shouldn't have to multiply in and divide out that 100.

Code:
float percent = [COLOR="Red"][B](float)[/B][/COLOR]totalBytesWritten / totalBytesExpectedToWrite;
 
Register on MacRumors! This sidebar will go away, and you'll see fewer ads.