NSOperationQueue addOperationWithBlock with return in iOS - objective-c

I have written method -(void) getStatus:(NSString*) url with return type,
-(NSString) getStatus:(NSString*) statusUrl
{
NSURL *urlObj = [NSURL URLWithString:[statusUrl
stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
NSError *err;
NSString *response = [NSString stringWithContentsOfURL: urlObj encoding:
NSUTF8StringEncoding error:&err];
return response;
}
But stringWithContentsOfURL is performing operation in main Thread, So while calling this method application struck for a second.
So I need to perform stringWithContentsOfURL function in background thread and after getting the response i want to return the response.
My current code:
-(NSString) getStatus:(NSString*) statusUrl
{
NSURL *urlObj = [NSURL URLWithString:[statusUrl
stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
NSError *err;
NSOperationQueue *rbQueue = [[[NSOperationQueue alloc] init] autorelease];
[rbQueue addOperationWithBlock:^{
NSString *response = [NSString stringWithContentsOfURL: urlObj
encoding: NSUTF8StringEncoding error:&err];
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
return response;
}];
}];
return #"";
}
But now me receiving a empty string #"", I can not receive response i got from server. Is there any way to do the same task..

Have you noticed that there should be some changes with this approach to complete the task? You hardly can use getters this way because of the nature of asynchronous methods. Or the getters will block the main thread in their turn.
To avoid this I could recommend you to use NSNotification to update the UI after you complete the server request in the background thread;
or change your getter method definition to pass the result from the background thread to the main thread, but asynchronously:
- (void)getStatusAsychronously:(NSString *)statusUrl withCompletionBlock:(void(^)(NSString *result))completionBlock;
or even consider subclassing NSOperation object for server request. The NSOperation subclasses are really handy with NSOperationQueue instances and could provide some more useful features like the cancellation of operation.

Related

Data transfer between NSOperations

I would like to obtain the following: I have two NSOperations in a NSOperationQueue. The firs is a download from a website (gets some json data) the next is parsing that data. This are dependent operations.
I don't understand how to link them together. If they are both allocated and in the queue, how do I transfer the json string to the operation that parses it? Is it a problem if this queue is inside another NSOperationQueue that executes an NSOperation that consists of the two mentioned previously?
All I could find is transfers of data to a delegate on the main thread (performSelectorOnMainThread), but I need all this operations to execute in the background.
Thanks.
Code:
NSDownload : NSOperation
- (instancetype)initWithURLString:(NSString *)urlString andDelegate:(id<JSONDataDelegate>)delegate
{
self = [super init];
if (self) {
_urlStr = urlString;
_delegate = delegate; /// this needs to be a NSOPeration
_receivedData = [NSMutableData dataWithCapacity:256];
}
return self;
}
#pragma mark - OVERRIDE
- (void)main
{
#autoreleasepool {
if (self.isCancelled) {
return;
}
NSURL *url = [NSURL URLWithString:self.urlStr];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
self.urlConnection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:YES];
}
}
#pragma mark - NSURLConnectionDataDelegate
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
if (self.isCancelled) {
[connection cancel];
self.receivedData = nil;
return;
}
[self.receivedData appendData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
if (self.isCancelled) {
self.receivedData = nil;
return;
}
// return data to the delegate
NSDictionary *responseDict = #{JSON_REQUESTED_URL : self.urlStr,
JSON_RECEIVED_RESPONSE : self.receivedData};
[(NSObject *)self.delegate performSelectorOnMainThread:#selector(didReceiveJSONResponse:) withObject:responseDict waitUntilDone:NO]; // ok to uses performSelector as this data is not for use on the main thread ???
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
// return error to the delegate
[(NSObject *)self.delegate performSelectorOnMainThread:#selector(didFailToReceiveDataWithError:) withObject:error waitUntilDone:NO];
}
#user1028028:
Use the following approach.
1) Maintain the operation queue reference that you are using to add DownloadOperation.
2) In connectionDidFinishLoading method, create ParseOperation instance, set the json data and add it to operation queue. Maintain ParseOperation strong reference variable in DownloadOperation and handling of cancelling of parsing operation through DownloadOperation interface.
3) After completed parsing call the UI functionality in main thread.
I hope this helps.
As lucianomarisi notes, it would usually be best to just have the first operation generate the second operation. This is usually simpler to manage. Operation dependencies aren't really that common in my experience.
That said, it's of course possible to pass data between operations. For instance, you could create a datasource property on the second operation. That would be the object to ask for its data; that object would be the first operation. This approach may require locking, though.
You can also create a nextOp property on the first operation. When it completes, it would call setData: on the second operation before exiting. You probably wouldn't need locking for this, but you might. In most cases it would be better for the first operation to just schedule the nextOp at this point (which again looks like lucianomarisi's answer).
The point is that an operation is just an object. It can have any methods and properties you want on it. And you can pass one operation to another.
Keep in mind that since an operation runs in the background, there's no reason you need to use the asynchronous interface to NSURLConnection. The synchronous API (sendSynchronousRequest:returningResponse:error: is fine for this, and much simpler to code. You could even use a trivial NSBlockOperation. Alternately, you can use the asynchronous NSURLConnection interface, but then you really don't need an NSOperation.
I also notice:
_receivedData = [NSMutableData dataWithCapacity:256];
Is it really such a small piece of JSON data? It's hard to believe that this complexity is worth it to move such a small parsing operation to the background.
(As a side note, unless you know precisely the size of the memory, there's not usually much benefit to specifying a capacity manually. Even then it's not always clear that it's a benefit. I believe NSURLConnection is using dispatch data under the covers now, so you're actually requesting a memory block that will never be used. Of course Cocoa also won't allocate it because it optimizes that out... the point is that you might as well just use [NSMutableData data]. Cocoa is quite smart about these kinds of things; you generally can only get in the way of its optimizations.)
As Rob said, unless you have any particular reason to use operations use the synchronized call. Then perform the selector on MainThread or on any other thread you need. Unless you want to separate the retrieval and parsing in separate operations or thread (explicitly).
Here is the code I was using for json retrieval and parsing:
-(BOOL) loadWithURL:(NSString*) url params: (NSDictionary*) params andOutElements:(NSDictionary*) jElements
{
NSError *reqError = nil;
NSString* urlStr = #"";//#"http://";
urlStr = [urlStr stringByAppendingString:url];
NSURL* nsURL = [NSURL URLWithString:urlStr];
//Private API to bypass certificate ERROR Use only for DEBUG
//[NSURLRequest setAllowsAnyHTTPSCertificate:YES forHost:[nsURL host]];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL: nsURL
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:60.0];
[request setHTTPMethod:#"POST"];
NSString *postString = #"";
if(params!=nil) {
NSEnumerator* enumerator = params.keyEnumerator;
NSString* aKey = nil;
while ( (aKey = [enumerator nextObject]) != nil) {
NSString* value = [params objectForKey:aKey];
//Use our own encoded implementation instead of above Apple one due to failing to encode '&'
NSString* escapedUrlString =[value stringByAddingPercentEscapesUsingEncoding:NSASCIIStringEncoding];
//Required to Fix Apple bug with not encoding the '&' to %26
escapedUrlString = [escapedUrlString stringByReplacingOccurrencesOfString: #"&" withString:#"%26"];
//this is custom append method. Please implement it for you -> the result should be 'key=value' or '&keyNotFirst=value'
postString = [self appendCGIPairs:postString key:aKey value:escapedUrlString isFirst:false];
}
}
//************** Use custom enconding instead !!!! Error !!!!! **************
[request setHTTPBody:[postString dataUsingEncoding:NSUTF8StringEncoding]];
NSData *response = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:&reqError];
if(reqError!=nil) {
NSLog(#"SP Error %#", reqError);
return NO;
}
NSString *json_string = [[NSString alloc] initWithData:response encoding:NSUTF8StringEncoding];
//Handles Server Errors during execution of the web service that handles the call.
if([json_string hasPrefix:#"ERROR"] == YES){
NSLog(#"SP Error %#", lastError);
return NO;
}
//Very Careful!!!!!! Will stop for any reason.!!!!!!
//Handles errors from IIS Server that serves teh request.
NSRange range = [json_string rangeOfString:#"Runtime Error"];
if(range.location != NSNotFound) {
NSLog(#"SP Error %#", lastError);
return NO;
}
//Do the parsing
jElements = [[parser objectWithString:json_string error:nil] copy];
if([parser error] == nil) {
NSLog(#"Parsing completed");
} else {
jElements = nil;
NSLog(#"Json Parser error: %#", parser.error);
NSLog(#"Json string: %#", json_string);
return NO;
}
//Parsed JSON will be on jElements
return YES;
}

Downloading multiple files in batches in iOS

I have an app that right now needs to download hundreds of small PDF's based on the users selection. The problem I am running into is that it is taking a significant amount of time because every time it has to open a new connection. I know that I could use GCD to do an async download, but how would I go about doing this in batches of like 10 files or so. Is there a framework that already does this, or is this something I will have to build my self?
This answer is now obsolete. Now that NSURLConnection is deprecated and NSURLSession is now available, that offers better mechanisms for downloading a series of files, avoiding much of the complexity of the solution contemplated here. See my other answer which discusses NSURLSession.
I'll keep this answer below, for historical purposes.
I'm sure there are lots of wonderful solutions for this, but I wrote a little downloader manager to handle this scenario, where you want to download a bunch of files. Just add the individual downloads to the download manager, and as one finishes, it will kick off the next queued one. You can specify how many you want it to do concurrently (which I default to four), so therefore there's no batching needed. If nothing else, this might provoke some ideas of how you might do this in your own implementation.
Note, this offers two advantages:
If your files are large, this never holds the entire file in memory, but rather streams it to persistent storage as it's being downloaded. This significantly reduces the memory footprint of the download process.
As the files are being downloaded, there are delegate protocols to inform you or the progress of the download.
I've attempted to describe the classes involved and proper operation on the main page at the Download Manager github page.
I should say, though, that this was designed to solve a particular problem, where I wanted to track the progress of downloads of large files as they're being downloaded and where I didn't want to ever hold the entire in memory at one time (e.g., if you're downloading a 100mb file, do you really want to hold that in RAM while downloading?).
While my solution solves those problem, if you don't need that, there are far simpler solutions using operation queues. In fact you even hint at this possibility:
I know that I could use GCD to do an async download, but how would I go about doing this in batches of like 10 files or so. ...
I have to say that doing an async download strikes me as the right solution, rather than trying to mitigate the download performance problem by downloading in batches.
You talk about using GCD queues. Personally, I'd just create an operation queue so that I could specify how many concurrent operations I wanted, and download the individual files using NSData method dataWithContentsOfURL followed by writeToFile:atomically:, making each download it's own operation.
So, for example, assuming you had an array of URLs of files to download it might be:
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount = 4;
for (NSURL* url in urlArray)
{
[queue addOperationWithBlock:^{
NSData *data = [NSData dataWithContentsOfURL:url];
NSString *filename = [documentsPath stringByAppendingString:[url lastPathComponent]];
[data writeToFile:filename atomically:YES];
}];
}
Nice and simple. And by setting queue.maxConcurrentOperationCount you enjoy concurrency, while not crushing your app (or the server) with too many concurrent requests.
And if you need to be notified when the operations are done, you could do something like:
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount = 4;
NSBlockOperation *completionOperation = [NSBlockOperation blockOperationWithBlock:^{
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[self methodToCallOnCompletion];
}];
}];
for (NSURL* url in urlArray)
{
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
NSData *data = [NSData dataWithContentsOfURL:url];
NSString *filename = [documentsPath stringByAppendingString:[url lastPathComponent]];
[data writeToFile:filename atomically:YES];
}];
[completionOperation addDependency:operation];
}
[queue addOperations:completionOperation.dependencies waitUntilFinished:NO];
[queue addOperation:completionOperation];
This will do the same thing, except it will call methodToCallOnCompletion on the main queue when all the downloads are done.
By the way, iOS 7 (and Mac OS 10.9) offer URLSession and URLSessionDownloadTask, which handles this quite gracefully. If you just want to download a bunch of files, you can do something like:
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration];
NSString *documentsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
NSFileManager *fileManager = [NSFileManager defaultManager];
for (NSString *filename in self.filenames) {
NSURL *url = [baseURL URLByAppendingPathComponent:filename];
NSURLSessionTask *downloadTask = [session downloadTaskWithURL:url completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {
NSString *finalPath = [documentsPath stringByAppendingPathComponent:filename];
BOOL success;
NSError *fileManagerError;
if ([fileManager fileExistsAtPath:finalPath]) {
success = [fileManager removeItemAtPath:finalPath error:&fileManagerError];
NSAssert(success, #"removeItemAtPath error: %#", fileManagerError);
}
success = [fileManager moveItemAtURL:location toURL:[NSURL fileURLWithPath:finalPath] error:&fileManagerError];
NSAssert(success, #"moveItemAtURL error: %#", fileManagerError);
NSLog(#"finished %#", filename);
}];
[downloadTask resume];
}
Perhaps, given that your downloads take a "significant amount of time", you might want them to continue downloading even after the app has gone into the background. If so, you can use backgroundSessionConfiguration rather than defaultSessionConfiguration (though you have to implement the NSURLSessionDownloadDelegate methods, rather than using the completionHandler block). These background sessions are slower, but then again, they happen even if the user has left your app. Thus:
- (void)startBackgroundDownloadsForBaseURL:(NSURL *)baseURL {
NSURLSession *session = [self backgroundSession];
for (NSString *filename in self.filenames) {
NSURL *url = [baseURL URLByAppendingPathComponent:filename];
NSURLSessionTask *downloadTask = [session downloadTaskWithURL:url];
[downloadTask resume];
}
}
- (NSURLSession *)backgroundSession {
static NSURLSession *session = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration backgroundSessionConfiguration:kBackgroundId];
session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:[NSOperationQueue mainQueue]];
});
return session;
}
#pragma mark - NSURLSessionDownloadDelegate
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location {
NSString *documentsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
NSString *finalPath = [documentsPath stringByAppendingPathComponent:[[[downloadTask originalRequest] URL] lastPathComponent]];
NSFileManager *fileManager = [NSFileManager defaultManager];
BOOL success;
NSError *error;
if ([fileManager fileExistsAtPath:finalPath]) {
success = [fileManager removeItemAtPath:finalPath error:&error];
NSAssert(success, #"removeItemAtPath error: %#", error);
}
success = [fileManager moveItemAtURL:location toURL:[NSURL fileURLWithPath:finalPath] error:&error];
NSAssert(success, #"moveItemAtURL error: %#", error);
}
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes {
// Update your UI if you want to
}
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite {
// Update your UI if you want to
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
if (error)
NSLog(#"%s: %#", __FUNCTION__, error);
}
#pragma mark - NSURLSessionDelegate
- (void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(NSError *)error {
NSLog(#"%s: %#", __FUNCTION__, error);
}
- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session {
AppDelegate *appDelegate = (id)[[UIApplication sharedApplication] delegate];
if (appDelegate.backgroundSessionCompletionHandler) {
dispatch_async(dispatch_get_main_queue(), ^{
appDelegate.backgroundSessionCompletionHandler();
appDelegate.backgroundSessionCompletionHandler = nil;
});
}
}
By the way, this assumes your app delegate has a backgroundSessionCompletionHandler property:
#property (copy) void (^backgroundSessionCompletionHandler)();
And that the app delegate will set that property if the app was awaken to handle URLSession events:
- (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)())completionHandler {
self.backgroundSessionCompletionHandler = completionHandler;
}
For an Apple demonstration of the background NSURLSession see the Simple Background Transfer sample.
If all of the PDFs are coming from a server you control then one option would be to have a single request pass a list of files you want (as query parameters on the URL). Then your server could zip up the requested files into a single file.
This would cut down on the number of individual network requests you need to make. Of course you need to update your server to handle such a request and your app needs to unzip the returned file. But this is much more efficient than making lots of individual network requests.
Use an NSOperationQueue and make each download a separate NSOperation. Set the maximum concurrent operations property on your queue to however many downloads you want to be able to run simultaneously. I'd keep it in the 4-6 range personally.
Here's a good blog post that explains how to make concurrent operations.
http://www.dribin.org/dave/blog/archives/2009/05/05/concurrent_operations/
What came as a big surprise is how slow dataWithContentsOfURL is when downloading multiple files!
To see it by yourself run the following example:
(you don't need the downloadQueue for downloadTaskWithURL, its there just for easier comparison)
- (IBAction)downloadUrls:(id)sender {
[[NSOperationQueue new] addOperationWithBlock:^{
[self download:true];
[self download:false];
}];
}
-(void) download:(BOOL) slow
{
double startTime = CACurrentMediaTime();
NSURLSessionConfiguration* config = [NSURLSessionConfiguration defaultSessionConfiguration];
static NSURLSession* urlSession;
if(urlSession == nil)
urlSession = [NSURLSession sessionWithConfiguration:config delegate:nil delegateQueue:nil];
dispatch_group_t syncGroup = dispatch_group_create();
NSOperationQueue* downloadQueue = [NSOperationQueue new];
downloadQueue.maxConcurrentOperationCount = 10;
NSString* baseUrl = #"https://via.placeholder.com/468x60?text=";
for(int i = 0;i < 100;i++) {
NSString* urlString = [baseUrl stringByAppendingFormat:#"image%d", i];
dispatch_group_enter(syncGroup);
NSURL *url = [NSURL URLWithString:urlString];
[downloadQueue addOperationWithBlock:^{
if(slow) {
NSData *urlData = [NSData dataWithContentsOfURL:url];
dispatch_group_leave(syncGroup);
//NSLog(#"downloaded: %#", urlString);
}
else {
NSURLSessionDownloadTask* task = [urlSession downloadTaskWithURL:url completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) {
//NSLog(#"downloaded: %#", urlString);
dispatch_group_leave(syncGroup);
}];[task resume];
}
}];
}
dispatch_group_wait(syncGroup, DISPATCH_TIME_FOREVER);
double endTime = CACurrentMediaTime();
NSLog(#"Download time:%.2f", (endTime - startTime));
}
There is nothing to "build". Just loop through the next 10 files each time in 10 threads and get the next file when a thread finishes.

objective-c: returning data from server

The following are methods that I am using to retrieve data from a server while displaying a UIActivityIndicator. I'm trying to put these methods in the app delegate and then call them from other classes, but I don't know how to return my JSONData. Can anybody help with this?
-(void)startProcess:(NSString *)buildURL{
UIActivityIndicatorView *aInd = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActionSheetStyleBlackTranslucent];
[aInd setFrame:CGRectMake(0, 0, 50, 50)];
[aInd startAnimating];
// then call the timeCOnsumingmethod in separate thread.
[NSThread detachNewThreadSelector:#selector(getData:) toTarget:self withObject:buildURL];
}
- (void)getData:(NSString *)buildURL{
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
// Query our database for a restaurant's menus
NSURL *url = [NSURL URLWithString:buildURL];
NSError *e;
NSString *jsonreturn = [[NSString alloc] initWithContentsOfURL:url encoding:NSUTF8StringEncoding error:&e];
NSData *jsonData = [jsonreturn dataUsingEncoding:NSUTF32BigEndianStringEncoding];
// NSError *error = nil;
[self performSelectorOnMainThread:#selector(endProcess:) withObject:jsonData waitUntilDone:YES];
[pool release];
//return jsonData;
}
- (IBAction)endProcess:(NSData *)jsonData{
// ??????????
return jsonData;
}
Not sure why got downvoted but your approach is all wrong. Here's what you want to do:
Add the UIActivityIndicatorView
Use NSURLConnection to asynchronously retrieve the data
Use NSJSONSerialization to decode the received JSON into a NSDictionary or NSArray
Remove the UIActivityIndicatorView
Your best bet would be to implement this as a separate class that takes a delegate object. You could implement a delegate protocol to indicate states like 'started network activity' (which your delegate could use to add a spinner view), and 'received data' (which would pass the decoded object back to the delegate - the delegate could then remove the spinner).
One of the benefits of this approach is you can easily set it up so that the connection/request is canceled when the object deallocs. Then you just store the request object as a property on your delegate, and when your delegate goes away, it deallocs the request, which cancels/cleans up properly.

Constantly calling a method

So I have a method that checks for internet connection, but only during the -(id):init method. Can I set it up so that it constantly checks for connection? If it helps, here is the code:
- (id) checkConnected
{
NSError *error = nil;
NSString *URLString = [NSString stringWithContentsOfURL:[NSURL URLWithString:#"http://www.google.com"] encoding:NSASCIIStringEncoding error:&error];
if (URLString != NULL)
{
connected = YES;
}
else connected = NO;
if(connected == YES)
NSLog(#"Connected");
else if (connected == NO)
NSLog(#"NotConnected");
return self;
}
While Reachability is a good first-pass check as others have suggested, it only tests the negative case: is it impossible to make a connection? If a firewall is blocking you, or the remote server is down, or any of a thousand other things happens, Reachability might tell you a system is in principle reachable (i.e. you have a network connection and the host if routeable) but the host is not in fact reachable.
So for some applications what you are asking is not unreasonable. The thing you have to be careful about is not to block your main thread with constant tests. Here is some code that will repeatedly run tests in the background:
NSURL *url = [NSURL URLWithString:#"http://www.google.com"];
NSURLRequest *req = [NSURLRequest requestWithURL:url];
__block NSHTTPURLResponse *response = nil;
__block NSError *error = nil;
dispatch_queue_t netQueue = dispatch_queue_create("com.mycompany.netQueue", DISPATCH_QUEUE_SERIAL);
dispatch_async(netQueue, ^{
while (! [NSURLConnection sendSynchronousRequest:req returningResponse:&response error:&error]) {
NSLog(#"Connection failed.");
}
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"Connection succeeded");
});
});
dispatch_release(netQueue);
Where "Connection succeeded" is logged you could instead write some main thread code that runs when a connection is successful. Note that I am passing in *response and *error from outside the block so they too will be available on your main thread inside or outside the block (assuming you keep them in scope) for your use.
You may want to throttle (i.e. just not use while()), but this is an implementation detail. Using NSTimer() as Richard suggested would work.
Finally, even with this code you still need to handle a potential failure of a subsequent connection. Just because it worked once doesn't mean the connection is available a millisecond later.

ASIHttp Synchronous request is running delegate methods after returning

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.