I've got the problem when I tried to do asynchronous requests to server from background thread. I've never got results of those requests. Simple example which shows the problem:
#protocol AsyncImgRequestDelegate
-(void) imageDownloadDidFinish:(UIImage*) img;
#end
#interface AsyncImgRequest : NSObject
{
NSMutableData* receivedData;
id<AsyncImgRequestDelegate> delegate;
}
#property (nonatomic,retain) id<AsyncImgRequestDelegate> delegate;
-(void) downloadImage:(NSString*) url ;
#end
#implementation AsyncImgRequest
-(void) downloadImage:(NSString*) url
{
NSURLRequest *theRequest=[NSURLRequest requestWithURL:[NSURL URLWithString:url]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:20.0];
NSURLConnection *theConnection=[[NSURLConnection alloc] initWithRequest:theRequest delegate:self];
if (theConnection) {
receivedData=[[NSMutableData data] retain];
} else {
}
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
[delegate imageDownloadDidFinish:[UIImage imageWithData:receivedData]];
[connection release];
[receivedData release];
}
#end
Then I call this from main thread
asyncImgRequest = [[AsyncImgRequest alloc] init];
asyncImgRequest.delegate = self;
[self performSelectorInBackground:#selector(downloadImage) withObject:nil];
method downloadImage is listed below:
-(void) downloadImage
{
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
[asyncImgRequest downloadImage:#"http://photography.nationalgeographic.com/staticfiles/NGS/Shared/StaticFiles/Photography/Images/POD/l/leopard-namibia-sw.jpg"];
[pool release];
}
The problem is that method imageDownloadDidFinish is never called. Moreover none of methods
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse*)response
are called. However if I replace
[self performSelectorInBackground:#selector(downloadImage) withObject:nil];
by
[self performSelector:#selector(downloadImage) withObject:nil];
everything is working correct. I assume that the background thread dies before async request is finished it job and this causes the problem but I'm not sure. Am I right with this assumptions? Is there any way to avoid this problem?
I know I can use sync request to avoid this problem but it's just simple example, real situation is more complex.
Thanks in advance.
Yes, the thread is exiting. You can see this by adding:
-(void)threadDone:(NSNotification*)arg
{
NSLog(#"Thread exiting");
}
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(threadDone:)
name:NSThreadWillExitNotification
object:nil];
You can keep the thread from exiting with:
-(void) downloadImage
{
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
[self downloadImage:urlString];
CFRunLoopRun(); // Avoid thread exiting
[pool release];
}
However, this means the thread will never exit. So you need to stop it when you're done.
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
CFRunLoopStop(CFRunLoopGetCurrent());
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
CFRunLoopStop(CFRunLoopGetCurrent());
}
Learn more about Run Loops in the Threading Guide and RunLoop Reference.
You can start the connection on a background thread but you have to ensure the delegate methods are called on main thread. This cannot be done with
[[NSURLConnection alloc] initWithRequest:urlRequest
delegate:self];
since it starts immediately.
Do this to configure the delegate queue and it works even on secondary threads:
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:urlRequest
delegate:self
startImmediately:NO];
[connection setDelegateQueue:[NSOperationQueue mainQueue]];
[connection start];
NSURLRequests are completely asynchronous anyway. If you need to make an NSURLRequest from a thread other than the main thread, I think the best way to do this is just make the NSURLRequest from the main thread.
// Code running on _not the main thread_:
[self performSelectorOnMainThread:#selector( SomeSelectorThatMakesNSURLRequest )
withObject:nil
waitUntilDone:FALSE] ; // DON'T block this thread until the selector completes.
All this does is shoot off the HTTP request from the main thread (so that it actually works and doesn't mysteriously disappear). The HTTP response will come back into the callbacks as usual.
If you want to do this with GCD, you can just go
// From NOT the main thread:
dispatch_async( dispatch_get_main_queue(), ^{ //
// Perform your HTTP request (this runs on the main thread)
} ) ;
The MAIN_QUEUE runs on the main thread.
So the first line of my HTTP get function looks like:
void Server::get( string queryString, function<void (char*resp, int len) > onSuccess,
function<void (char*resp, int len) > onFail )
{
if( ![NSThread isMainThread] )
{
warning( "You are issuing an HTTP request on NOT the main thread. "
"This is a problem because if your thread exits too early, "
"I will be terminated and my delegates won't run" ) ;
// From NOT the main thread:
dispatch_async( dispatch_get_main_queue(), ^{
// Perform your HTTP request (this runs on the main thread)
get( queryString, onSuccess, onFail ) ; // re-issue the same HTTP request,
// but on the main thread.
} ) ;
return ;
}
// proceed with HTTP request normally
}
Related
I have a http request to make whenever a new location has been found asynchronously, for handling request, i have create a class called background requester which takes care of all these requests. The following code sample is as follows.
- (void)locationManager:(CLLocationManager *)manager
didUpdateToLocation:(CLLocation *)newLocation
fromLocation:(CLLocation *)oldLocation
{
dispatch_queue_t queue;
queue = dispatch_queue_create("com.test.sample", NULL); //create a serial queue can either be null or DISPATCH_QUEUE_SERIAL
dispatch_async(queue,
^{
if (bgTask == UIBackgroundTaskInvalid)
{
bgTask=[[UIApplication sharedApplication]
beginBackgroundTaskWithExpirationHandler:
^{
DDLogInfo(#"Task =%d",bgTask);
DDLogInfo(#"Ending bground task due to time expiration");
[[UIApplication sharedApplication] endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
}
BackgroundRequester *request = [[BackgroundRequester alloc] initwithLocation:self.currentLocation];
[request start];
DDLogInfo(#"Task =%d",bgTask);
DDLogInfo(#"bg Task remaining time=%f",[[UIApplication sharedApplication] backgroundTimeRemaining]);
});
}
//background requester class
//the start function will inturn calll the callAsynchrnously method.
-(void) callAsynchronously:(NSString *)url
{
DDLogInfo(#"Calling where am i from background");
DDLogInfo(#"Url =%#",reqURL);
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url] cachePolicy:NSURLCacheStorageNotAllowed timeoutInterval:20.0f];
responseData = [[NSMutableData alloc] init];
connect = [NSURLConnection connectionWithRequest:request delegate:self];
[connect start];
}
You cannot use connectionWithRequest from a background queue (without scheduling the connection in some runloop). Either
use sendSynchronousRequest (which is fine to do if you're using it from a background queue like this), or
schedule the connection in a run loop. If you dig through the AFNetworking code, you'll see they create a run loop on a dedicated thread, which strikes me as the most elegant solution if you really need the NSURLConnectionDataDelegate methods.
You can also use the main run loop (though I'm less crazy about that solution), e.g., something like:
connect = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO];
[connect scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
[connect start];
Also note, I'm not using connectionWithRequest (because that starts the connection immediately, which is incompatible with your calling of start; only use start if you use initWithRequest with a startImmediately of NO). If you try to do start in conjunction with connectionWithRequest, it can cause problems.
I think the sendSynchronousRequest is simplest (and saves you from having to write any of the NSURLConnectionDataDelegate methods, too). But if you need the NSURLConnectionDataDelegate methods (e.g. you need progress updates, you're using a streaming protocol, etc.), then use the scheduleInRunLoop method.
I have an app which downloads some files from the server in few threads. The problems is that it is giving a heavy load to the CPU (hitting to 80%). What can be done to make it better? I made similar app on Windows with C#, and the cpu usage never goes above 5%.
EDIT: This code has been changed after getting some suggestions below. The problem now is, that the download never reaches 100% when I set [queue setMaxConcurrentOperationCount:6]. If I change the asynchronous NSURLConnection back to sendSynchronous call it works, when I change the above OperationCount to 1, also works.
This is how I add NSOperations to the queue (may be large, like 800).
int chunkId = 0;
for (DownloadFile *downloadFile in [download filesInTheDownload])
{
chunkId = 0;
for (DownloadChunk *downloadChunk in [downloadFile chunksInTheFile])
{
DownloadChunkOperation *operation = [[DownloadChunkOperation alloc] initWithDownloadObject:download
downloadFile:downloadFile downloadChunk:downloadChunk andChunkId:chunkId];
[queue addOperation:operation];
chunkId++;
}
}
#import "DownloadChunkOperation.h"
#import "Download.h"
#import "DownloadFile.h"
#import "DownloadChunk.h"
#interface DownloadChunkOperation()
#property(assign) BOOL isExecuting;
#property(assign) BOOL isFinished;
#end
#implementation DownloadChunkOperation
#synthesize download = _download;
#synthesize downloadFile = _downloadFile;
#synthesize downloadChunk = _downloadChunk;
#synthesize isFinished = _isFinished;
#synthesize isExecuting = _isExecuting;
- (id) initWithDownloadObject:(Download *)download downloadFile:(DownloadFile *)downloadFile downloadChunk:(DownloadChunk *)downloadChunk andChunkId:(uint32_t)chunkId
{
self = [super init];
if (self) {
self.download = download;
self.downloadFile = downloadFile;
self.downloadChunk = downloadChunk;
self.chunkId = chunkId;
}
return self;
}
- (void) start
{
if ([self isCancelled]) {
[self setIsFinished:YES];
[self setIsExecuting:NO];
return;
}
[self setIsExecuting:YES];
[self setIsFinished:NO];
[self.downloadChunk setChunkState:cDownloading];
downloadPath = [[NSString stringWithFormat:#"%#/%#", [self.download downloadFolder], [self.download escapedTitle]] stringByExpandingTildeInPath];
NSURL *fileURL = [[NSURL alloc] initWithString:[self.downloadFile filePath]];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:fileURL];
NSString *range = [NSString stringWithFormat:#"bytes=%lli-%lli", [self.downloadChunk startingByte], [self.downloadChunk endingByte]];
[request setValue:range forHTTPHeaderField:#"Range"];
connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO];
// IMPORTANT! The next line is what keeps the NSOperation alive for the during of the NSURLConnection!
[connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[connection start];
if (connection) {
NSLog(#"connection established!");
do {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
} while (!self.isFinished);
} else {
NSLog(#"couldn't establish connection for: %#", fileURL);
}
}
- (BOOL) isConcurrent
{
return YES;
}
- (void) connection:(NSURLConnection *)_connection didReceiveResponse:(NSURLResponse *)response
{
receivedData = [[NSMutableData alloc] init];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
// Not cancelled, receive data.
if (![self isCancelled]) {
[receivedData appendData:data];
self.download.downloadedBytes += [data length];
return;
}
// Cancelled, tear down connection.
[self setIsExecuting:NO];
[self setIsFinished:YES];
[self.downloadChunk setChunkState:cConnecting];
[self->connection cancel];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
[self setIsExecuting:NO];
[self setIsFinished:YES];
NSLog(#"Connection failed! Error - %# %#",
[error localizedDescription],
[[error userInfo] objectForKey:NSURLErrorFailingURLStringErrorKey]);
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
NSString *chunkPath = [downloadPath stringByAppendingFormat:#"/%#.%i", [self.downloadFile fileName], self.chunkId];
NSError *saveError = nil;
[receivedData writeToFile:chunkPath options:NSDataWritingAtomic error:&saveError];
if (saveError != nil) {
NSLog(#"Download save failed! Error: %#", [saveError description]);
}
else {
NSLog(#"file has been saved!: %#", chunkPath);
}
[self setIsExecuting:NO];
[self setIsFinished:YES];
[self.downloadChunk setChunkState:cFinished];
if ([self.download downloadedBytes] == [self.download size])
[[NSNotificationCenter defaultCenter] postNotificationName:#"downloadFinished" object:self.download];
}
#end
You should not create threads yourself. Use dedicated API like NSOperationQueue or even GCD directly for this purpose. They know better about hardware limits, virtual cores, etc. and support priority settings.
You shouldn't use +sendSynchronousRequest: either. Wrapping your -downloadChunk method in a dispatch call as suggested by charith won't help you improve performance, as +sendSynchronousRequest: blocks the thread until new data comes in and forces GCD to spawn new threads.
Use the asynchronous API of NSURLConnection using delegate callbacks. You can also wrap your NSURLConnection code inside a NSOperation subclass and use NSOperationQueue to manage the downloads: Using NSURLConnections
If you don't want to write the NSOperation subclass yourself, you can also use a 3rd party framework like AFNetworking.
Try with GCD blocks and global queues. This is the apple recommended way now for concurrency ex:
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(globalQueue, ^{
[self downloadChunk:objDownload];
});
I've read through tons of messages saying the same thing all over again : when you use a NSURLConnection, delegate methods are not called. I understand that Apple's doc are incomplete and reference deprecated methods, which is a shame, but I can't seem to find a solution.
Code for the request is there :
// Create request
NSURL *urlObj = [NSURL URLWithString:url];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:urlObj cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:30];
[request setValue:#"gzip" forHTTPHeaderField:#"Accept-Encoding"];
if (![NSURLConnection canHandleRequest:request]) {
NSLog(#"Can't handle request...");
return;
}
// Start connection
dispatch_async(dispatch_get_main_queue(), ^{
self.connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:YES]; // Edited
});
...and code for the delegate methods is here :
- (void) connection:(NSURLConnection *)_connection didReceiveResponse:(NSURLResponse *)response {
NSLog(#"Receiving response: %#, status %d", [(NSHTTPURLResponse*)response allHeaderFields], [(NSHTTPURLResponse*) response statusCode]);
self.data = [NSMutableData data];
}
- (void) connection:(NSURLConnection *)_connection didFailWithError:(NSError *)error {
NSLog(#"Connection failed: %#", error);
[self _finish];
}
- (void) connection:(NSURLConnection *)_connection didReceiveData:(NSData *)_data {
[data appendData:_data];
}
- (void)connectionDidFinishDownloading:(NSURLConnection *)_connection destinationURL:(NSURL *) destinationURL {
NSLog(#"Connection done!");
[self _finish];
}
There's not a lot of error checking here, but I've made sure of a few things :
Whatever happens, didReceiveData is never called, so I don't get any data
...but the data is transfered (I checked using tcpdump)
...and the other methods are called successfully.
If I use the NSURLConnectionDownloadDelegate instead of NSURLConnectionDataDelegate, everything works but I can't get a hold on the downloaded file (this is a known bug)
The request is not deallocated before completion by bad memory management
Nothing changes if I use a standard HTML page somewhere on the internet as my URL
The request is kicked off from the main queue
I don't want to use a third-party library, as, ultimately, these requests are to be included in a library of my own, and I'd like to minimize the dependencies. If I have to, I'll use CFNetwork directly, but it will be a huge pain in the you-know-what.
If you have any idea, it would help greatly. Thanks!
I ran into the same problem. Very annoying, but it seems that if you implement this method:
- (void)connectionDidFinishDownloading:(NSURLConnection *)connection destinationURL:(NSURL *)destinationURL
Then connection:didReceiveData: will never be called. You have to use connectionDidFinishLoading: instead... Yes, the docs say it is deprecated, but I think thats only because this method moved from NSURLConnectionDelegate into NSURLConnectionDataDelegate.
I like to use the sendAsynchronousRequest method.. there's less information during the connection, but the code is a lot cleaner.
[NSURLConnection sendAsynchronousRequest:request queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error){
if (data){
//do something with data
}
else if (error)
NSLog(#"%#",error);
}];
From Apple:
By default, a connection is scheduled on the current thread in the
default mode when it is created. If you create a connection with the
initWithRequest:delegate:startImmediately: method and provide NO for
the startImmediately parameter, you can schedule the connection on a
different run loop or mode before starting it with the start method.
You can schedule a connection on multiple run loops and modes, or on
the same run loop in multiple modes.
Unless there is a reason to explicitly run it in [NSRunLoop currentRunLoop],
you can remove these two lines:
[connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
[connection start];
or change the mode to NSDefaultRunLoopMode
NSURLConnection API says " ..delegate methods are called on the thread that started the asynchronous load operation for the associated NSURLConnection object."
Because dispatch_async will start new thread, and NSURLConnection will not pass to that other threat the call backs, so do not use dispatch_async with NSURLConnection.
You do not have to afraid about frozen user interface, NSURLConnection providing only the controls of asynchronous loads.
If you have more files to download, you can start some of connection in first turn, and later they finished, in the connectionDidFinishLoading: method you can start new connections.
int i=0;
for (RetrieveOneDocument *doc in self.documents) {
if (i<5) {
[[NSURLConnection alloc] initWithRequest:request delegate:self];
i++;
}
}
..
-(void)connectionDidFinishLoading:(NSURLConnection *)connection
{
ii++;
if(ii == 5) {
[[NSURLConnection alloc] initWithRequest:request delegate:self];
ii=0;
}
}
One possible reason is that the outgoing NSURLRequest has been setup to have a -HTTPMethod of HEAD. Quite hard to do that by accident though!
I need to make some POST calls to my server, but I need to not block the main thread. As I understand, NSMutableURLRequest and NSURLConnection are not thread safe, so it is best to use the async method of NSURLConnection.
My question about this is, how I can package it up nicely into a method, instead of having to use the delegate method? I would prefer to do:
NSData *returnedData = [Utility postDataToURL:#"some string of data"];
This is how it is easy done with the following method:
[NSURLConnection sendSynchronousRequest:serviceRequest returningResponse:&serviceResponse error:&serviceError];
It is so nice keeping everything within one method, then just having my data returned from it!
Are there any block based methods for this? It becomes an issue when I need to write methods for about 50 different calls and each one needs to use the same delegate method. Am I going about this the wrong way?
This will only need to be for iOS5.
iOS 5 adds sendAsynchronousRequest:queue:completionHandler: which does what I think you want. I've got my code set up to use that if available, but to fall back on performing a synchronous fetch on a background GCD queue and hopping onto the main thread with the result if it doesn't. The latter will be less power efficient but it's just to maintain legacy support.
if([NSURLConnection respondsToSelector:#selector(sendAsynchronousRequest:queue:completionHandler:)])
{
// we can use the iOS 5 path, so issue the asynchronous request
// and then just do whatever we want to do
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:
^(NSURLResponse *response, NSData *data, NSError *error)
{
[self didLoadData:data];
}];
}
else
{
// fine, we'll have to do a power inefficient iOS 4 implementation;
// hop onto the global dispatch queue...
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
^{
// ... perform a blocking, synchronous URL retrieval ...
NSError *error = nil;
NSURLResponse *urlResponse = nil;
NSData *responseData =
[NSURLConnection sendSynchronousRequest:request returningResponse:&urlResponse error:&error];
// ... and hop back onto the main queue to handle the result
dispatch_async(dispatch_get_main_queue(),
^{
[self didLoadData:responseData];
});
});
}
In production code you'd actually check the errors and HTTP response codes (as a server 404 response is probably just as much an error from your point of view as a connection failure), obviously.
iOS 5.0 > you can use sendAsynchronousRequest method look at NSURLConnection Class and it uses blocks. If you want to support iOS 4.0 > too then you have to write one of your own block based Asynchronous URL loading which is fairly easy to write. You are better off by using MKNetworkKit.
but I need to not block the main thread. As I understand, NSMutableURLRequest and NSURLConnection are not thread safe, so it is best to use the async method of NSURLConnection.
You don't want to do Synchronous network connection it blocks thread whichever it is called from (its even worse if its main thread). You can do Asynchronous network connection on main thread. If you want to do call NSURLConnection on non-main thread then have to create a RunLoop on that thread (if you don't then the delegate methods of NSURLConnection never gets called).
I had this problem pre-5.0 so I made a little class to handle the NSURLConnection delegate protocol and offer callers an interface with closures:
BetterNSURLConnection.h
#property (retain, nonatomic) NSURLRequest *request;
BetterNSURLConnection.m
#property (retain, nonatomic) NSURLConnection *connection;
#property (retain, nonatomic) NSHTTPURLResponse *response;
#property (retain, nonatomic) NSMutableData *responseData;
#property (copy, nonatomic) void (^completionBlock)(id, NSHTTPURLResponse *);
#property (copy, nonatomic) void (^errorBlock)(NSError *);
... You can add typedefs to make those block signatures prettier...then:
#synthesize connection = _connection;
#synthesize response = _response;
#synthesize responseData = _responseData;
#synthesize completionBlock = _completionBlock;
#synthesize errorBlock = _errorBlock;
#synthesize request=_request;
- (void)startWithCompletion:(void (^)(id, NSHTTPURLResponse *))completionBlock error:(void (^)(NSError *))errorBlock {
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
self.completionBlock = completionBlock;
self.errorBlock = errorBlock;
self.responseData = [NSMutableData data];
NSURLConnection *connection = [NSURLConnection connectionWithRequest:self.request delegate:self];
self.connection = connection;
[self.connection start];
[connection release];
}
... then do the delegate like this:
- (void)connection:(NSURLConnection *)aConnection didReceiveResponse:(NSHTTPURLResponse *)response {
[self.responseData setLength:0];
self.response = response;
}
- (void)connection:(NSURLConnection *)aConnection didReceiveData:(NSData *)data {
[self.responseData appendData:data];
}
- (void)connection:(NSURLConnection *)aConnection didFailWithError:(NSError *)error {
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
self.errorBlock(error);
self.connection = nil;
}
- (void)connectionDidFinishLoading:(NSURLConnection *)aConnection {
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
if (self.response.statusCode >= 400) {
self.errorBlock(error);
} else {
// i do json requests, and call a json parser here, but you might want to do something different
id result = [self parseResponse:self.responseData];
self.completionBlock(result, self.response);
}
self.connection = nil;
}
I use a facade method to op-queue an internal worker which issues the synchronous call. Depending on the rate at which you send the calls, it might work. Example:
// Presented in #interface
-(void)sendPostRequest {
// Last chance to update main thread/UI
NSInvocationOperation *op = [[[NSInvocationOperation alloc] initWithTarget:self selector:#selector(sendPostRequest_internal) object:nil] autorelease];
[opQueue addOperation:op];
}
// Hidden in #implementation
-(void)sendPostRequest_internal {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSURLRequest *request = // yadda, you might use NSURLMutableRequest
NSURLResponse *response = nil;
NSError *error = nil;
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:response error:error];
// process data, retain things as needed, post results using performSelectorOnMainThread:
[pool release];
}
It works pretty well for my purposes but you might need to delve a little deeper in to the async stuff, which really isn't too bad.
I am trying to download a file from a server. My code is following. In didFinishLaunchingWithOptions method, I create a new thread using detachNewThreadSelector which runs the following code.
NSString *destPath = [self.home_dir_path stringByAppendingPathComponent:[NSString stringWithFormat:#"_%#",content_data_file_name]];
[ContentBO downloadFile:destPath content_name:content_data_file_name];
if([self updatesAvailable]){
//update content
}else{
//launch app
}
My code for downloadFile is:
#try{
NSString *url = [NSString stringWithFormat:#"%#/%#",ServerURL,content_name];
NSLog(#"downloading URL is: %#",url);
self.request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:[url stringByAddingPercentEscapesUsingEncoding:NSASCIIStringEncoding]]];
[self.request setRequestMethod:#"GET"];
[self.request setDownloadDestinationPath:destFilePath];
NSLog(#"destination path is: %#",destFilePath);
[self.request setTimeOutSeconds:30];
[self.request setDelegate:self];
[self.request startSynchronous];
NSError *error = [self.request error];
NSData *receivedData = nil;
if (!error) {
isSuccess = YES;
self.responseStr = [request responseString];
receivedData = [NSData dataWithData:[self.request responseData]];
}
else {
isSuccess = NO;
NSLog(#"The following error occurred: %#", error);
}
}#catch(NSException *e){
NSLog(#"exception occured.");
}
What my understanding about synchronous call is that this is a blocking call and control should not go below
[ContentBO downloadFile:destPath content_name:content_data_file_name];
until control is out of requestFinished method of ASIHTTPRequestDelegate. In my case what happening is that the control is simultaneously executing code in requestFinished and below
[ContentBO downloadFile:destPath content_name:content_data_file_name];
But I don't want the control to go below [ContentBO downloadFile...] before coming out of requestFinished method.
The requestFinished delegate call is run on the main thread asynchronously, and your code is not running on the main thread, so it is expected that both would run at the same time.
However, as you are using synchronous requests why not remove the contents of requestFinished and put the code after the 'startSyncronous' line? You are guaranteed the request has finished when startSynchronous returns.
In one of my projects the app had to do heavy server side data syncing. In that process one operation should had start after the successful execution of it's previous process and I was using ASIHttp synchronous requests in that. I was facing the same issue you mentioned, so to tackle it I used NSCondiiton. All it requires that you lock the thread after you call:
[self.request startSynchronous];. When the requests delegate method is called after the exection of the request, issue a signal command, and the next line of the code after the thread lock statement will be executed. Here is a rough example:
//declare a pointer to NSCondition in header file:
NSCondition *threadlock;
-(id) init
{
threadlock = [[NSCondition alloc] init]; //release it in dealloc
}
-(void)downLoadFile
{
[thread lock];
//your request code
[self.request setDidFinishSelector:#selector(downLoadFileRequestDone:)];
[self.request setDidFailSelector:#selector(downLoadFileRequestWentWrong:)];
[self.request startSynchronous];
[thread wait];
//the lines here will be executed after you issue a signal command in the request delegate method
[thread unlock];
}
-(void) downLoadFileRequestDone:(ASIHTTPRequest *)request
{
[thread lock];
//perform desire functionality
//when you are done call:
[thread signal];
[thread unlock];
}
It worked perfect for me... hope it will help.