NSURLConnection delegation and threading - iPhone - iphone

I have a class that updates two .plist files in the app documents directory via an NSURLConnection. The class acts as its own delegate for NSURLConnection. It works properly when I ask for a single file, but fails when I try to update two files. Does it look like I should start a new thread for each of the getNewDatabase messages?
- (void)getAllNewDatabases {
[self performSelectorOnMainThread:#selector(getNewDatabase:) withObject:#"file1" waitUntilDone:YES];
[self performSelectorOnMainThread:#selector(getNewDatabase:) withObject:#"file2" waitUntilDone:YES];
}
- (BOOL)getNewDatabase:(NSString *)dbName
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSMutableString *apiString = [[NSMutableString alloc] initWithString:kAPIHost];
[apiString appendFormat:#"/%#.plist",dbName];
NSURLRequest *myRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:apiString] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:60.0];
NSURLConnection *myConnection = [[NSURLConnection alloc] initWithRequest:myRequest delegate:self];
[apiString release];
if( myConnection )
{
//omitted for clarity here
}
[pool release];
}
//NSURLConnection delegate methods here ...

I found something interesting with NSURLConnection and NSThread - the thread will only live as long as it takes to perform the method that you call from it.
In the case above the thread will live only as long as getNewDatabase:(NSString *)dbName takes to complete, therefore killing off any of its delegate methods before they actually have time to do anything.
I found this website that gives a better explanation and a solution to the problem
I tweaked it a little bit so I could have a custom time out if it didn't complete in a given time frame (handy when someone is walking around between access points)
start = [NSDate dateWithTimeIntervalSinceNow:3];
while(!isFinished && [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
beforeDate:[NSDate distantFuture]]){
if([start compare:[NSDate date]] == NSOrderedAscending){
isFinished = YES;
}
}

As it stands currently in the code you provided, getNewDatabase: is running on the main thread of your application. The problem in this particular case then is something other than the life cycle of the thread, as James observed in his case.
If you did intend to perform this operation in the background, I'd recommend looking into using NSOperationQueue and NSOperation rather than solving the problem with the current code. I think your case is a great fit for NSOperationQueue, especially given that you have more than one download task to perform.
Dave Dribin has an excellent article about using asynchronous API, such as NSURLConnection, inside an NSOperation. Alternatively, as long as you're running in a background thread, you can also simplify the process and just use a synchronous API method instead in your NSOperation, such as initWithContentsOfURL:.
Marcus Zarra has also written a tutorial that demonstrates how easy it is to incorporate and use NSOperationQueue for simple background operations.

Related

How to perform an asynchronous request on a background thread?

I have a method foo: that is called on a background thread. This method simply sends a request to a server, and, after data are retrieved, performs some calculations about those data and returns. In this case I prefer to use sendSynchronousRequest: because this method is convenient and it doesn't matter if the thread is blocked. However, the response contains a "Location" header field that will redirect to another page. I want to read the response to get those "Set-Cookie" header fields before redirection. It seems that the synchronous method does not allow me to.
I tried to use the asynchronous one and implement a NSURLConnectionDataDelegate, but the thread is finished before those methods of the delegate is called. (I suppose the way that Apple implements the asynchronous one is to perform those time-consuming works on a new thread)
Is there any way to solve this problem? Since performing an asynchronous request on the main thread may add complexity to my program.
The foo: method is kind of like this
- (Result *)foo
{
NSURLMutableRequest * request = blablabla;
//Do something to initialize the request
NSData *data = [NSURLConnection sendSynchronousRequest:request
returningResponse:&response
error:&error];
//Do something with the data
Result *result = [[Result alloc] init] autorelease];
//fill the result
return result;
}
You could use a Grand Central Dispatch semaphore to wait until the asynchronous request returns:
- (Result *)foo
{
NSMutableURLRequest * request = [[NSMutableURLRequest alloc] init];
// set request's properties here
__block Result *result;
dispatch_semaphore_t holdOn = dispatch_semaphore_create(0);
[NSURLConnection sendAsynchronousRequest:request queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if (error)
{
// handle error
}
else
{
result = [[Result alloc] initWithData:data];
}
dispatch_semaphore_signal(holdOn);
}];
dispatch_semaphore_wait(holdOn, DISPATCH_TIME_FOREVER);
return result;
}
NOTE: This code requires iOS 4.0+ and ARC!
Look into [NSCondition] which enables you to wait and signal threads
Basically you allocate a NSCondition and in the block you'll have [condition wait]; which will cause the thread to wait. then, when the async operation is done, you call [condition signal]; which will signal the waiting thread to continue.
http://developer.apple.com/DOCUMENTATION/Cocoa/Reference/NSCondition_class/Reference/Reference.html
You can create your own NSRunLoop and do your requests there. Stop the run loop once you're done with your requests.
Or if you are lazy like me and don't want to mess with run loops, just put your connection on the main thread:
dispatch_async(dispatch_get_main_queue(), ^(void){
self.connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
[connection start];
}
You can find a small and simple class that lets you do this on github. It provides two primary objects - a NSOperationsQueue manager and NSOperation subclasses designed to run background fetches. As was mentioned, if all you were doing was fetches, you could do that on the main thread. But if you want to do data processing too, then this project will let you do that in a completed method.
A nice property of the OperationsRunner class is that you can cancel operations at any time (when for instance the user taps the back button), and everything gets torn down quickly with no stalling or leaking.
However, if all you ever do is this one fetch and one process, then you could as others have said just fetch the data on the main thread, and once you have it then dispatch a "processing" block to one of the concurrent system threads, and when that processing is done, dispatch another message to the main thread telling you that the work is complete.

Need to use NSOperationQueue to parse two different NSOperation class

Trying to parse two different URLs which has XML data.
static NSString *string1 = #"http://abc.com/abc1.xml";
NSURLRequest *URL1 =[NSURLRequest requestWithURL:[NSURL URLWithString:string1]];
self.URL1Connection =[[[NSURLConnection alloc] initWithRequest:URL1 delegate:self] autorelease];
static NSString *string2 = #"http://abc.com/abc2.xml";
NSURLRequest *URL2 =[NSURLRequest requestWithURL:[NSURL URLWithString:string2]];
self.URL2Connection =[[[NSURLConnection alloc] initWithRequest:URL2 delegate:self] autorelease];
I have two different NSOperation class both working independently as both have their own work to finish.
I have a parseQueue which is NSOperationqueue in which I have added two different operations.
TestOperation *testOperation = [[TestOperation alloc]
initWithData:self.data1 delegate:self ];
[self.parseQueue addOperation:testOperation];
[testOperation release]; // once added to the NSOperationQueue it's retained, we don't need it anymore
testOperation = nil;
Test1Operation *test1Operation = [[Test1Operation alloc]
initWithData:self.data2];
[self.parseQueue addOperation:test1Operation];
[test1Operation release]; // once added to the NSOperationQueue it's retained, we don't need it anymore
test1Operation = nil;
Basically I am trying to parse the two xml data separately and want to have concurrent operations. But when the second operation gets over adding in the queue, it still looks at the first class operation. I am lost in this since I have no idea why it is still looking for the first class even after release. Can anybody throw some ideas and help me.
I figured out the answer.
Need to call each XML URL in their own respective class and call NSOperation for each call seperately. Instead of calling on application delegate method, call on viewdidload or viewdidappear method as required.
Once finished parsing, notify the main thread that the parsing is over and return the result.
Sagos

NSURLConnection Problem with NSOperationqueu

Hi I am trying to create a NSOperaion Queue to download a bunch of PDF files. But it doesnt work. The delegate methods dont get called for NSURLConnection since i put them in NSOperation queue.... any alternatives or solution???
- (void) loadData {
NSOperationQueue *queue = [NSOperationQueue new];
NSInvocationOperation *operation;
for(int i=0;i<[self.pdfArray count];i++){
NSString *url = [NSString stringWithFormat:#"http://www.somelink.com/%#.pdf",[self.pdfArray objectAtIndex:i]];
operation = [[NSInvocationOperation alloc] initWithTarget:self
selector:#selector(loadDataWithOperation:)
object:url];
[queue addOperation:operation];
[operation release];
}
}
- (void) loadDataWithOperation:(NSString *) url{
// Create the request.
NSURLRequest *theRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:url]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:60.0];
NSURLConnection *theDownload = [[NSURLConnection alloc] initWithRequest:theRequest delegate:self startImmediately:YES];
}
take a look here, this is a tutorial that was helpful to me so I bookmarked it
http://www.dribin.org/dave/blog/archives/2009/05/05/concurrent_operations/
I can't really see a problem with the code but it might be a threading issue. NSOperationQueue creates a thread through Grand Central Dispatch to run the operation. If NSURLConnection then also tries to create a thread it might cause an issue - I'm not sure a thread can be a child of a child thread.
You could do an sendSynchronousRequest: so that it stays in the thread that you have created in the NSOperationQueue and see if that works better.
NSURLConnection needs a running NSRunLoop to function. If you call NSURLConnection methods on a thread whose NSRunLoop is not running, the NSURLConnection will never run. The worker threads that NSOperationQueue creates don't have their NSRunLoops running. Nor could you guarantee that the thread would still exist when the NSURLConnection received a response from the server.
It is fine to call NSURLConnection methods from a background thread, but it needs to be a thread whose lifetime you can guarantee and it needs to have its NSRunLoop running.

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!

NSURLConnection - how to wait for completion

Our iPhone app code currently uses NSURLConnection sendSynchronousRequest and that works fine except we need more visibility into the connection progress and caching so we're moving to an async NSURLConnection.
What's the simplest way to wait for the async code to complete? Wrap it in a NSOperation/NSOperationQueue, performSelector..., or what?
Thanks.
I'm answering this in case anyone else bumps into the issue in the future. Apple's URLCache sample code is a fine example of how this is done. You can find it at:
iOS Developer Library - URLCache
As John points out in the comment above - don't block/wait - notify.
To use NSURLConnection asynchronously you supply a delegate when you init it in initWithRequest:delegate:. The delegate should implement the NSURLConnection delegate methods. NSURLConnection processing takes place on another thread but the delegate methods are called on the thread that started the asynchronous load operation for the associated NSURLConnection object.
Apart from notifications mentioned prior, a common approach is to have the class that needs to know about the URL load finishing set itself as a delegate of the class that's handling the URL callbacks. Then when the URL load is finished the delegate is called and told the load has completed.
Indeed, if you blocked the thread the connection would never go anywhere since it works on the same thread (yes, even if you are using the asynch methods).
I ran into this because our app used NSURLConnection sendSynchronousRequest in quite a few places where it made sense, like having some processing occurring on a background thread occasionally needing extra data to complete the processing. Something like this:
// do some processing
NSData * data = someCachedData;
if (data = nil) {
data = [NSURLConnection sendSynchronousRequest....]
someCachedData = data;
}
// Use data for further processing
If you have something like 3 different places in the same flow that do that, breaking it up into separate functions might not be desirable(or simply not doable if you have a large enough code base).
At some point, we needed to have a delegate for our connections(to do SSL certificate pinning) and I went trolling the internet for solutions and everything was of the form: "just use async and don't fight the framework!". Well, sendSynchronousRequest exists for a reason, this is how to reproduce it with an underlying async connection:
+ (NSData *)sendSynchronousRequest:(NSURLRequest *)request returningResponse:(NSURLResponse *__autoreleasing *)response error:(NSError *__autoreleasing *)error
{
static NSOperationQueue * requestsQueue;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
requestsQueue = [[NSOperationQueue alloc] init];
requestsQueue.maxConcurrentOperationCount = NSOperationQueueDefaultMaxConcurrentOperationCount;
});
NSCondition * waitLock = [NSCondition new];
[waitLock lock];
__block NSError * returnedError;
__block NSURLResponse * returnedResponse;
__block NSData * returnedData;
__block BOOL done = NO;
[NSURLConnection sendAsynchronousRequest:request
queue:requestsQueue
completionHandler:^(NSURLResponse * response, NSData * data, NSError * connectionError){
returnedError = connectionError;
returnedResponse = response;
returnedData = data;
[waitLock lock];
done = YES;
[waitLock signal];
[waitLock unlock];
}];
if (!done) {
[waitLock wait];
}
[waitLock unlock];
*response = returnedResponse;
*error = returnedError;
return returnedData;
}
Posted here in case anyone comes looking as I did.
Note that NSURLConnection sendAsynchrounousRequest can be replaced by whatever way you use to send an async request, like creating an NSURLConnection object with a delegate or something.