UIProgressView, Theading, performSelectorOnMainThread while using ASIHTTPRequest - iphone

Kinda stuck on this problem and I'm not sure, where I've gone wrong. Heres what I'm doing:
Class calls:
- (void)updateApplicationDataInBackground {
updateView = [[UpdatingView alloc] init];
[self.view addSubview:updateView.view];
DataSynchronizer *dataSynchronizer = [[DataSynchronizer alloc] init];
[NSThread detachNewThreadSelector:#selector(initWithDataRequest:) toTarget:dataSynchronizer withObject:self];
[dataSynchronizer release];
This creates a thread to retrieve data from the server and parse it. In DataSynchronizer this is the method being called:
- (void)initWithDataRequest:(id)parent {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
controller = parent;
NSLog(#"DataSynchronizer initWithDataRequest called");
NSURL *url = [NSURL URLWithString: ApiUrl];
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
[request setPostValue:ApiKey forKey:#"key"];
[request setPostValue:ApiPass forKey:#"password"];
[request setPostValue:#"somevalue" forKey:#"framework"];
[request setPostValue:#"somevalue" forKey:#"method"];
[request setDidFinishSelector:#selector(parseResult:)];
[request setDidFailSelector:#selector(requestError:)];
[request setTimeOutSeconds:60];
[request setDelegate:self];
[request startAsynchronous];
[pool release];
After my data is received I parse the contents and do my data synch. This is all working as expected. I've decided to throw in a UIProgressView so the user can see what is going on with this request, this progress view lives in updateView which is created in the updateApplicationDataInBackground.
I'm not trying to show progress for the web service call but simply when milestones are reached in the data processing. In the DidFinishSelector its calling parseResult
There are five method its calls with the response data:
[self parseData:[data objectForKey:#"types"] forObject:[Types class] andParent:nil];
[controller performSelectorOnMainThread:#selector(updateProgress:) withObject:[NSNumber numberWithFloat:.4] waitUntilDone:YES];
After each process I'm trying to update the UIProgressView, it will never update. Now if I simply call performSelectorOnMainThread from outside the ASIHTTPRequest it works as expected, but not within the DidFinishSelector. I've tried many variations on this where it calls a local method which updates the mainThread, where I simply use performSelector. Nothing works, how do I update the the UIProgessView?
Is the problem a thread spawning a thread?
Thanks
EDIT:
Looks like the DidFinishSelector is being called on the main thread already. I've updated my code to simply call:
[controller updateProgress:[NSNumber numberWithFloat:.8]]
Still no luck....
Realized it might be helpful to see the UIProgessView update method.
- (void)updateProgress:(NSNumber *)progress {
float newProgess = [progress floatValue];
[updateView.myProgress setProgress: newProgess];

Ok so it looks like I found my own answer after changing somethings around. Because ASIHttpRequest performs SetDidFinish selector on the main thread my calls performSelectorOnMainThread weren't doing anything. I changed my initial call for the DataSynchronizer to the main thread and added changed the DidFinish method to:
- (void)parseDataInBackground:(ASIHTTPRequest *)request {
[NSThread detachNewThreadSelector:#selector(parseResult:) toTarget:self withObject:request];
Which then makes the parse method run on separate thread (since its the bulk of the processing and now performOnMainThread works without issue.

Related

Implementation of Async Request causing many leaks

I've inherited a project that uses of ASIHttpRequest for all network communication. I am unclear as to which specific version we're using. All I can tell is that, from the .h files, the oldest creation date on a particular file is 17/08/10 (ASIDataDecompressor).
We're using completion and failure blocks. For some reason, the failure block is often triggered, which should only really happen if the server fails to respond. Our logs look sane, and we haven't received any notifications (Airbrake) that there were server problems around the time the errors occur, so for now I'm moving forward with the assumption that our server is fine and it's the app that is the culprit.
I decided to run the app through Instruments (Leaks) and was astonished to see that when I force a request to fail, ~27 leaks are created immediately. I'm don't know how to get around Instruments all that well, so I'm not really sure what to do with the information now that I have it.
I figured I'd post my code to see if there's anything glaring.
In viewDidLoad, this code is executed
[[MyAPI sharedAPI] getAllHighlights:pageNumber:perPage onSuccess:^(NSString *receivedString,NSString *responseCode) {
[self getResults:receivedString];
if(![responseCode isEqualToString:#"Success"]) {
[self hideProgressView];
appDelegate.isDiscover_RefreshTime=YES;
[[MyAPI sharedAPI] showAlert:responseCode];
} else {
NSString *strLogEvent=#"Discover_Highlights_Loaded Page_";
strLogEvent=[strLogEvent stringByAppendingFormat:#"%i",intPageNumber];
[FlurryAnalytics logEvent:strLogEvent timed:YES];
}
} onFail:^(ASIFormDataRequest *request) {
NSDictionary *parameters = [[MyAPI sharedAPI] prepareFailedRequestData:request file:#"Discover" method:_cmd];
[FlurryAnalytics logEvent:#"Unable_to_Connect_to_Server" withParameters:parameters timed:true];
[self hideProgressView];
appDelegate.isDiscover_RefreshTime=YES;
[[AfarAPI sharedAPI] showAlert:#"Unable to Connect to Server."];
[tblHighlightsGrid reloadData];
[tblListHighlights reloadData];
}];
These typedefs have been defined at the top of API Singleton:
typedef void (^ASIBasicBlockWrapper)(NSString *responseString,NSString *responseCode);
typedef void (^ASIBasicBlockWrapperFail)(ASIFormDataRequest *request);
MyAPISingleton#getAllHighlights...
- (void)getAllHighlights:(NSString *)pageNumber:(NSString *)perPage onSuccess:(ASIBasicBlockWrapper)cb1 onFail:(ASIBasicBlockWrapperFail)cb2{
NSString *access_token= [[NSUserDefaults standardUserDefaults] objectForKey:#"access_token"];
NSString *url = [baseURL stringByAppendingFormat:AFAR_GET_ALL_HIGHLIGHTS_ENDPOINT, pageNumber,perPage];
if (access_token) { url = [url stringByAppendingFormat:ACCESS_TOKEN, access_token]; }
__block ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:[NSURL URLWithString:url]];
[request setRequestMethod:#"GET"];
[request setDelegate:self];
[self executeAsynchronousRequest:request onSuccess:cb1 onFail:cb2];
}
And finally, MyAPI#executeAsynchronousRequest:
- (void) executeAsynchronousRequest:(ASIFormDataRequest *)request onSuccess:(ASIBasicBlockWrapper)cb1 onFail:(ASIBasicBlockWrapperFail)cb2
{
[request setCompletionBlock:^{
int statusCode = [request responseStatusCode];
NSString *statusMessage = [self statusErrorMessage:statusCode];
cb1([request responseString],statusMessage);
}];
[request setFailedBlock:^{
cb2(request);
}];
[request startAsynchronous];
}
Does anything stand out as to why 27 leaks are created?
I figured this out.
The ASIHttpRequest Documentation is very clear about the fact that you need to designate your request object with the __block storage mechanism:
Note the use of the __block qualifier when we declare the request, this is important! It tells the block not to retain the request, which is important in preventing a retain-cycle, since the request will always retain the block.
In getAllHighlights(), I'm doing that, but then I'm sending my request object as an argument to another method (executeAsyncRequest). The __block storage type can only be declared on local variables, so in the method signature, request is just typed to a normal ASIFormDataRequest, and so it seems as though it loses its __block status.
The trick is to cast (I'm not sure if that's technically accurate) the argument before using it in a block.
Here's my leak free implementation of executeAsyncRequest:
- (void) executeAsyncRequest:(ASIFormDataRequest *)request onSuccess:(ASIBasicBlockWrapper)cb1 onFail:(ASIBasicBlockWrapperFail)cb2
{
// this is the important part. now we just need to make sure
// to use blockSafeRequest _inside_ our blocks
__block ASIFormDataRequest *blockSafeRequest = request;
[request setCompletionBlock: ^{
int statusCode = [blockSafeRequest responseStatusCode];
NSString *statusMessage = [self statusErrorMessage:statusCode];
cb1([blockSafeRequest responseString],statusMessage);
}];
[request setFailedBlock: ^{
cb2(blockSafeRequest);
}];
[request startAsynchronous];
}

NSThread is blocking my GUI

I use a NSThread in order to download videos and images from a server side.It work looks and works great except the fact that when the downloading is done my GUI gets blocked until the download is complete.When the download is finished it takes a few seconds to work again.
this is how the server request is done:
- (void) repeatRequest{
NSLog(#"repeatRequest");
[NSThread detachNewThreadSelector:#selector(backgroundRequest) toTarget:self withObject:nil];
}
- (void) backgroundRequest{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSURL *url = [NSURL URLWithString:myURLStr];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setDelegate:self];
[request startAsynchronous];
[pool drain];
}
- (void)requestFinished:(ASIHTTPRequest *)request
{
//do things
}
IMPORTANTAnd I also tried to start the ASIHTTPRequest from the GUI thread but with the same behaviour.
Any idea about what could be wrong?
EDIT:
- (void) viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
[[UIApplication sharedApplication] setStatusBarHidden:YES];
//internetReachable = [[Reachability reachabilityForInternetConnection] retain];
if(timer1 == nil)
{
timer1 = [NSTimer scheduledTimerWithTimeInterval:60.0 target:self selector: #selector(repeatRequest) userInfo: nil repeats: YES];
}
}
Try to run synchronous ASIHTTPRequest in your background thread, and handle results not in delegate method (requestFinished), but after [request startSynchronous];
I don't know anything about ASIHTTPRequest but i would assume its -startAsynchronous method already handles the background downloading for you. It all likelihood, it is returning immediately and your new thread is exiting. Also, you should just use [pool release] at the end of a thread method instead of [pool drain], it will be drained upon release, and you won't be leaking an NSAutoReleasePool. Does ASIHTTPRequest have a -startSynchronous (or just plain -start) method? Try using that within -backgroundRequest, as it should block the premature exit of that thread.

Implement Asynchronous Download (NSURLConnection) on separate NSThread?

This may sounds weird but please bear with me. I have 6-7 API calls which make request to a server one by one. I want to implement these calls in a separate thread. But when I do this, none of my delegate methods (of NSURLConnection) gets called even after managing a separate NSRunloop
([[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];)
Can anyone suggests me alternative approach for the same or any correction in existing functionality??
Use ASIHTTPRequest instead. It's much easier to use than NSURLConnection.
a quick google threw up this: http://blog.emmerinc.be/index.php/2009/03/15/multiple-async-nsurlconnections-example/ he is using a dictionary to manage multiple requests
Using a separate thread for each NSURLConnection, which is already multithreaded is a bad idea. It's just pointlessly using system resources and defeating NSURLConnections attempts to manage connections optimally. However, it does work so if you are not receiving delegate messages you are doing something wrong. Rather than find an alternative way todo it you should try to get to the bottom of your problem with the runloop.
I'm using ASIHTTPRequest to do the similar operation. Go through the following code change the downloadAllIcons method to suit your requirement,
[NSThread detachNewThreadSelector:#selector(downloadAllIcons:) toTarget:self withObject:xmlData];
-(void) downloadAllIcons:(NSData *)_xmlData
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSArray *IconList= PerformXMLXPathQuery(_xmlData,#"//icon");
[[NSUserDefaults standardUserDefaults] setObject:IconList forKey:#"IconList"];
for (int i=0; i<[IconList count]; i++) {
if ([[NSUserDefaults standardUserDefaults] objectForKey:[[IconList objectAtIndex:i] objectForKey:#"nodeContent"]]==nil) {
NSData * responseData=[self downloadProccessedImage:[[IconList objectAtIndex:i] objectForKey:#"nodeContent"]];
if(responseData)
[[NSUserDefaults standardUserDefaults] setObject:responseData forKey:[[IconList objectAtIndex:i] objectForKey:#"nodeContent"]];
//NSLog(#"%#",[[IconList objectAtIndex:i] objectForKey:#"nodeContent"]);
}
}
[pool release];
}
-(id) downloadProccessedImage:(NSString *)_URL
{
NSData *response=nil;
NSURL *url = [NSURL URLWithString:_URL];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setTimeOutSeconds:60];
[ASIHTTPRequest setShouldThrottleBandwidthForWWAN:YES];
[request startSynchronous];
NSError *error = [request error];
if (!error)
{
response = [request responseData];
}
return response;
}

iOS Application Background Downloading

Hey! I need to know how I can have my iOS Application start a download in the background of the application (like, have the download run in the AppDelegate file) so changing ViewControllers will not interrupt or cancel the download. I also need to be able to get the progress of the download (0.00000 - 1.00000), to set a UIProgressView object to, which also means I need a - (void)progressDidChangeTo:(int)progress function.
Just use ASIHTTPRequest it is way easier than NSURLRequest and does exactly what you need.
It examples that shows how to download in background and how to report progress.
I wouldn't download anything in the AppDelegate directly. Instead I would create a separated class just for that purpose. Let's call it MyService I would then initialize that class in my app delegate.
The class can work as a singleton or can be passed to each view controller that requires it.
In MyService class I would add the ASINetworkQueue and few methods to handle the requests when they are ready. Here is the code from ASI examples that you can use:
- (IBAction)startBackgroundDownloading:(id)sender
{
if (!self.queue) {
self.queue = [[[ASINetworkQueue alloc] init] autorelease];
}
NSURL *url = [NSURL URLWithString:#"http://allseeing-i.com"];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setDelegate:self];
[request setDidFinishSelector:#selector(requestDone:)];
[request setDidFailSelector:#selector(requestWentWrong:)];
[self.queue addOperation:request]; //queue is an NSOperationQueue
[self.queue go];
}
- (void)requestDone:(ASIHTTPRequest *)request
{
NSString *response = [request responseString];
//Do something useful with the content of that request.
}
- (void)requestWentWrong:(ASIHTTPRequest *)request
{
NSError *error = [request error];
}
If you need to set the progress bar. I would just expose the setDownloadProgressDelegate of ASINetworkQueue in my MyService class and set it in my ViewControllers like that:
[[MyService service] setDownloadProgressDelegate: self.myUIProgressView];
BTW. If you need to continue downloading even when your app exits you can set ShouldContinueWhenAppEntersBackground property of your request to YES.
you can use NSURLConnection to start an asynchronous request that won't cause your UI to be frozen. You can do it by doing something like:
NSURLRequest *urlRequest = [[NSURLRequest alloc] initWithURL:url];
connection = [[NSURLConnection alloc] initWithRequest:urlRequest delegate:self];
[urlRequest release];
in order to have your progress you can use the:
connection:didReceiveResponse:(NSURLResponse *)response;
delegate call to inspect the response.expectedContentLength and then use the
connection:didReceiveData:(NSData *)data
to track the amount of data that was downloaded and calculate a percentage.
Hope this helps,
Moszi

NSOperation for drawing UI while parsing data?

Hope you guys can help me :)
In the main thread, I create a NSOperation and add it to a queue.
What that operation do is connect to a data server with NSURLConnection, save the receivedData and parse it.
Operation.m
- (void)start
{
NSLog(#"opeartion for <%#> started.", [cmd description]);
[self willChangeValueForKey:#"isExecuting"];
_isExecuting = YES;
[self didChangeValueForKey:#"isExecuting"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:_url];
[request setHTTPMethod:#"POST"];
[request setValue:[NSString stringWithFormat:#"multipart/form-data; boundary=%#", m_BOUNDARY] forHTTPHeaderField:#"Content-Type"];
[request setHTTPBody:_postData];
_connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
if (_connection == nil)
[self finish];
}
Then in this NSURL delegate method I parse the data I've just received from server.
Operation.m
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
[self parseItems];
}
In the data, I can found items like, for instance, screenItem, CellItem, TextItem that I send to the main thread for drawing them while arriving. (I create a UITableView if an itemTable arrives, or I create a UIWebView if an itemWeb arrives)
Using this for sending item to main thread:
Operation.m
- (void) parseItems
{
while ([_data length] > 0)
{
NSInteger type = [self _readByte];
switch (type)
{
case SCREEN:
{
[self _send: [self _readScreen]];
break;
}
case CELL:
{
[self _send: [self _readCell]];
break;
}
// ... A lot of different items
}
}
}
- (void)_send:(CItem*)_item
{
[[NSNotificationCenter defaultCenter] postNotificationName:#"newItem" object:_item];
}
Then in notification receiver:
AppDelegate.m
- (void) _newItemArrived:(NSNotification *) notification
{
[self performSelectorOnMainThread:#selector(processItem:) withObject:[notification object] waitUntilDone:NO];
}
My problem is that the UI is not painted until NSOperation finish. I thought that NSOpertion, being a different thread, would not block the main thread, but believe that is what is happening.
Some tips for this issue?
Thanks a lot for reading!
Are you using NSOperationQueue?
Check out this answer to the question NSOperation blocks UI painting? for a simple example of how to update the UI with a notification from an NSOperation running asynchronously on another thread.
UPDATE
NSURLConnection supports asynchronous connections by itself with a delegate. You should use this. If you have specific issue(s), you should describe those.
Check out the ASIHTTPRequest library.
If you really want to use this approach, you could trying running NSURLConnection synchronously (using the class method sendSynchronousRequest:returningResponse:error:). Your app would remain responsive since the connection is on a background thread. However, you would not be able to update anything until all the data is received.
So I know this is a pretty old question but I ran into the same issue and after hours of going through documentation and blogs I found a great solution in this post from Wim Haanstra http://www.depl0y.com/?p=345
Putting your NSOperation in an infinite loop until you get data back should do the trick!