Asynchronous NSURLConnection breaks timers - ios

I have a class set up to handle my web API calls. This is done using an NSMutableURLRequest and an NSRLlConnection. I initially used connectionWithRequest: delegate: and that worked well for the most part, except when I depended on this request being truly asynchronous, not just partially executing in the main run loop.
To do this, I thought I would just use the ever so convenient sendAsynchronousRequest: queue: completionHandler: and at first in all of my unit tests I thought this worked great. It performed asynchronously, my semaphores were waited on and signaled correctly, it was great.
Until I tried to re-use this new modified version of my Web service class in my actual app. Part of my app plays a video and uses a repeating NSTimer to update part of the screen based on the current playback time of the video. For some unknown reason, as long as I have executed at least one of these new asynchronous NSURLConnections both the video playback and the timer no longer work.
Here is how I initialize the connection:
[NSURLConnection sendAsynchronousRequest:requestMessage
queue:[[NSOperationQueue alloc] init]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError)
{
if ( data.length > 0 && connectionError == nil )
{
_webServiceData = [data mutableCopy];
[self performSelector:#selector(connectionDidFinishLoading:) withObject:nil];
}
else if ( connectionError != nil )
{
_webServiceData = [data mutableCopy];
[self performSelector:#selector(webServiceDidFinishExecutingWithError:) withObject:connectionError];
}
}];
Here is how I initialize my repeating timer:
playbackTimeTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(checkPlaybackTime) userInfo:nil repeats:YES];
And I have absolutely no idea why the asynchronous NSURLConnection is causing aspects of my app that are completely unrelated to stop functioning.
EDIT:
For clarification I have ViewControllerA that performs the web requests to retrieve some data. When that data is successfully retrieved, ViewControllerA automatically segues to ViewControllerB. In ViewControllerB's viewWillAppear is where I set up my movie player and timer.

In the future, do not use semaphores to make the test wait. Use the new XCTestExpectation, which is designed for testing asynchronous processes.
And unlike the traditional semaphore trick, using the test expectation doesn't block the main thread, so if you have completion blocks or delegates that require the main thread, you can do this in conjunction with the test expectation.
You're clearly doing something that isn't working because you're running this on the background queue. Typical problems include
If you were trying to run that timer from the background thread, that wouldn't work, unless you used one of the standard timer workarounds (scheduling it on main runloop, dispatching the creation of the timer back to main thread, using dispatch timer, etc.).
Having earlier described these alternatives in great detail, it turns out you're initiating the timer from viewWillAppear, so that's all academic.
any UI updates (including performing segues, reloading tables, etc.).
care should be taken when synchronizing class properties updated from background thread (these might best be dispatched to main thread, too).
Anyway, you might remedy this by just tell sendAsynchronousRequest to run its completion block on the main queue:
[NSURLConnection sendAsynchronousRequest:requestMessage
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError)
...
}];
Then, the completion block would have run on the background queue, and everything would probably be fine. Or you can manually dispatch the calling of your completion handlers back to the main queue.
But make sure that any UI updates (including the performing a segue programmatically) are run on the main thread (either by running the whole completion block on the main thread, or manually dispatching the relevant calls to the main thread).

Related

Running multiple background threads iOS

Is it possible to run multiple background threads to improve performance on iOS . Currently I am using the following code for sending lets say 50 network requests on background thread like this:
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
// send 50 network requests
});
EDIT:
After updating my code to something like this no performance gain was achieved :( Taken from here
dispatch_queue_t fetchQ = dispatch_queue_create("Multiple Async Downloader", NULL);
dispatch_group_t fetchGroup = dispatch_group_create();
// This will allow up to 8 parallel downloads.
dispatch_semaphore_t downloadSema = dispatch_semaphore_create(8);
// We start ALL our downloads in parallel throttled by the above semaphore.
for (NSURL *url in urlsArray) {
dispatch_group_async(fetchGroup, fetchQ, ^(void) {
dispatch_semaphore_wait(downloadSema, DISPATCH_TIME_FOREVER);
NSMutableURLRequest *headRequest = [NSMutableURLRequest requestWithURL:url cachePolicy: NSURLRequestUseProtocolCachePolicy timeoutInterval:60.0];
[headRequest setHTTPMethod: #"GET"];
[headRequest addValue: cookieString forHTTPHeaderField: #"Cookie"];
NSOperationQueue *queue = [[[NSOperationQueue alloc] init] autorelease];
[NSURLConnection sendAsynchronousRequest:headRequest
queue:queue // created at class init
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error){
// do something with data or handle error
NSLog(#"request completed");
}];
dispatch_semaphore_signal(downloadSema);
});
}
// Now we wait until ALL our dispatch_group_async are finished.
dispatch_group_wait(fetchGroup, DISPATCH_TIME_FOREVER);
// Update your UI
dispatch_sync(dispatch_get_main_queue(), ^{
//[self updateUIFunction];
});
// Release resources
dispatch_release(fetchGroup);
dispatch_release(downloadSema);
dispatch_release(fetchQ);
Be careful not to confuse threads with queues
A single concurrent queue can operate across multiple threads, and GCD never guarantees which thread your tasks will run on.
The code you currently have will submit 50 network tasks to be run on a background concurrent queue, this much is true.
However, all 50 of these tasks will be executed on the same thread.
GCD basically acts like a giant thread pool, so your block (containing your 50 tasks) will be submitted to the next available thread in the pool. Therefore, if the tasks are synchronous, they will be executed serially. This means that each task will have to wait for the previous one to finish before preceding. If they are asynchronous tasks, then they will all be dispatched immediately (which begs the question of why you need to use GCD in the first place).
If you want multiple synchronous tasks to run at the same time, then you need a separate dispatch_async for each of your tasks. This way you have a block per task, and therefore they will be dispatched to multiple threads from the thread pool and therefore can run concurrently.
Although you should be careful that you don't submit too many network tasks to operate at the same time (you don't say specifically what they're doing) as it could potentially overload a server, as gnasher says.
You can easily limit the number of concurrent tasks (whether they're synchronous or asynchronous) operating at the same time using a GCD semaphore. For example, this code will limit the number of concurrent operations to 6:
long numberOfConcurrentTasks = 6;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(numberOfConcurrentTasks);
for (int i = 0; i < 50; i++) {
dispatch_async(concurrentQueue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
[self doNetworkTaskWithCompletion:^{
dispatch_semaphore_signal(semaphore);
NSLog(#"network task %i done", i);
}];
});
}
Edit
The problem with your code is the line:
dispatch_queue_t fetchQ = dispatch_queue_create("Multiple Async Downloader", NULL);
When NULL is passed to the attr parameter, GCD creates a serial queue (it's also a lot more readable if you actually specify the queue type here). You want a concurrent queue. Therefore you want:
dispatch_queue_t fetchQ = dispatch_queue_create("Multiple Async Downloader", DISPATCH_QUEUE_CONCURRENT);
You need to be signalling your semaphore from within the completion handler of the request instead of at the end of the request. As it's asynchronous, the semaphore will get signalled as soon as the request is sent off, therefore queueing another network task. You want to wait for the network task to return before signalling.
[NSURLConnection sendAsynchronousRequest:headRequest
queue:queue // created at class init
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error){
// do something with data or handle error
NSLog(#"request completed");
dispatch_semaphore_signal(downloadSema);
}];
Edit 2
I just noticed you are updating your UI using a dispatch_sync. I see no reason for it to be synchronous, as it'll just block the background thread until the main thread has updated the UI. I would use a dispatch_async to do this.
Edit 3
As CouchDeveloper points out, it is possible that the number of concurrent network requests might be being capped by the system.
The easiest solution appears to be migrating over to NSURLSession and configuring the maxConcurrentOperationCount property of the NSOperationQueue used. That way you can ditch the semaphores altogether and just dispatch all your network requests on a background queue, using a callback to update the UI on the main thread.
I am not at all familiar with NSURLSession though, I was only answering this from a GCD stand-point.
You can send multiple requests, but sending 50 requests in parallel is usually not a good idea. There is a good chance that a server confronted with 50 simultaneous request will handle the first few and return errors for the rest. It depends on the server, but using a semaphore you can easily limit the number of running requests to anything you like, say four or eight. You need to experiment with the server in question to find out what works reliably on that server and gives you the highest performance.
And there seems to be a bit of confusion around: Usually all your network requests will run asynchronously. That is you send the request to the OS (which goes very quick usually), then nothing happens for a while, then a callback method of yours is called, processing the data. Whether you send the requests from the main thread or from a background thread doesn't make much difference.
Processing the results of these requests can be time consuming. You can process the results on a background thread. You can process the results of all requests on the same serial queue, which makes it a lot easier to avoid multithreading problems. That's what I do because it's easy and even in the worst case uses one processor for intensive processing of the results, while the other processor can do UI etc.
If you use synchronous network requests (which is a bad idea), then you need to dispatch each one by itself on a background thread. If you run a loop running 50 synchronous network requests on a background thread, then the second request will wait until the first one is completely finished.

Using NSUrlConnection inside either NSThread or NSOperation

I am developing a static library that needs to do some stuff in the background, without interacting with the main thread. To give you an idea, think of just logging some user events. The library must keep doing this stuff until the user exits the app or sends it to the background (pushes the home button) - in other words it needs to keep doing stuff inside a loop.
The only interaction between the main app thread and the spawned thread is that occasionally the main app thread will put some stuff (an event object) into a queue that the spawned thread can read/consume. Other than that, the spawned thread just keeps going until the app exists or backgrounds.
Part of what the spawned thread needs to do (though not all of it) involves sending data to an HTTP server. I would have thought that it would be easy to subclass NSThread, override its main method, and just make a synchronous call to NSUrlConnection with some sort of timeout on that connection so the thread doesn't hang forever. For example, in Java/Android, we just subclass Thread, override the start() method and call a synchronous HTTP GET method (say from Apache's HttpClient class). This is very easy and works fine. But from what I have seen here and elsewhere, apparently on iOS it is much more complicated than this and I'm more than a bit confused as to what the best approach is that actually works.
So should I subclass NSThread and somehow use NSUrlConnection? It seems the asynchronous NSUrlConnection does not work inside NSThread because delegate methods don't get called but what about the synchronous method? Do I somehow need to use and configure the RunLoop and set up an autorelease pool? Or should I use an NSOperation? It seems to me that what I am trying to do is pretty common - does anyone have a working example of how to do this properly?
As I understand it, to use NSURLConnection asynchronously you need a runloop. Even if you use an NSOperation you still need a runloop.
All the examples I have seen use the Main Thread to start NSURLConnection which has a runloop. The examples using NSOperation are set up so the operation is Concurrent which tells NSOperationQueue not to provide it's own thread, they then make sure that NSURLConnection is started on the main thread, for example via a call to performSelectorOnMainThread:
Here is an example:
Pulse Engineering Blog: Concurrent Downloads using NSOperationQueues
You can also search the Apple documentation for QRunLoopOperation in the LinkedImageFetcher sample which is an example class showing some ins and outs of this kind of thing.
(Although I'm not sure I actually saw any code that example showing how to run your own runloop, again this example relies on the main thread.)
I've used the grand central dispatch (GCD) methods to achieve this. Here is an example that worked for me in a simple test app (I'm not sure if it applies in a static library, but may be worth a look). I'm using ARC.
In the example, I am kicking off some background work from my viewDidLoad method, but you can kick it off from anywhere. The key is that "dispatch_async(dispatch_get_global_queue…" runs the block in a background thread. See this answer for a good explanation of that method: https://stackoverflow.com/a/12693409/215821
Here is my viewDidLoad:
- (void)viewDidLoad
{
[super viewDidLoad];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, (unsigned long)NULL),
^(void) {
[self doStuffInBackground];
});
}
The doStuffInBackground method is running in the background at this point, so you can just use NSURLConnection synchronously. In my example here, the method loops making network calls until presumably some other code sets backgroundStuffShouldRun = false. A network call is made with a 10 second timeout. After the call, I'm updating a UI label just to show progress. Note that the UI update is performed with "dispatch_async(dispatch_get_main_queue()…". This runs the UI update on the UI thread, as required.
One potential issue with this background work: there isn't a way to cancel the http request itself. But, with a 10 second timeout, you'd be waiting a max of 10 seconds for the thread to abort itself after an outsider (likely some event in your UI) sets backgroundStuffShouldRun = false.
- (void)doStuffInBackground
{
while (backgroundStuffShouldRun) {
// prepare for network call...
NSURL* url = [[NSURL alloc] initWithString:#"http://maps.google.com/maps/geo"];
// set a 10 second timeout on the request
NSURLRequest* request = [[NSURLRequest alloc] initWithURL:url cachePolicy:NSURLCacheStorageAllowed timeoutInterval:10];
NSError* error = nil;
NSURLResponse *response = nil;
// make the request
NSData* data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
// were we asked to stop the background processing?
if (!backgroundStuffShouldRun) {
return;
}
// process response...
NSString* status = #"Success";
if (error) {
if (error.code == NSURLErrorTimedOut) {
// handle timeout...
status = #"Timed out";
}
else {
// handle other errors...
status = #"Other error";
}
}
else {
// success, handle the response body
NSString *dataAsString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(#"%#", dataAsString);
}
// update the UI with our status
dispatch_async(dispatch_get_main_queue(), ^{
[statusLabel setText:[NSString stringWithFormat:#"completed network call %d, status = %#", callCount, status]];
});
callCount++;
sleep(1); // 1 second breather. not necessary, but good idea for testing
}
}

Why use async HTTP request over sync HTTP in a separate thread?

I know about the difference between how each works but i want to know in a performance wise point of view (resources inside the iphone).
Lets say I send an asynch request and wait for the delegate to be called. This won't lock my execution thread. But what is the difference of doing this against just sending a synch request in another thread with GCD.
Like this:
dispatch_queue_t findPicsQueue;
findPicsQueue = dispatch_queue_create("FindPicsQueue", NULL);
dispatch_async(findPicsQueue, ^{
NSData *theResponse = [NSURLConnection sendSynchronousRequest:theRequest
returningResponse:&response
error:&error];
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)response;
if (error) {
NSLog(#"Error: %#",error)
}
if (httpResponse.statusCode == 200)
{
[self parseXMLFile:theResponse]; // Parses Data and modifies picturesFound
for (PictureData *tmp in picturesFound) {
NSLog(#"%#",tmp);
}
}
}
It wont lock my interface since its not being executed in the main thread, but it will lock this specific thread. And I also think GCD runs queues concurrently.
Thanks in advance. I really want to clarify this question.
If you use NSURLConnection with sendAsynchronousRequest, then almost all processing takes place on the main thread, in particular, the XML parsing will be done on the main thread. Your code example however uses a different thread for processing.
This difference is relevant if you have an iPhone or iPad processor with two cores. Then the XML parsing can run in parallel with some UI activity on the main thread (in your example). So it can be completed earlier compared to running everything on the main thread (sendAsynchronousRequest approach).
For older devices with just one core, only one thread will run at a time and the two approaches should behave almost identical.

On iOS, can you make a synchronous network request (but not on the main thread) and still get progress callbacks (on a separate, non-main thread)?

On iOS, can you make a synchronous network request (off the main thread) and get progress callbacks (on a separate, non-main thread)?
I have have a serial (one-operation-at-a-time) background queue that runs all of time-consuming jobs that don't need to finish right now. I do want to show progress for the download jobs though. It doesn't look like you can instantiate an NSURLConnection and configure a delegate, start synchronous connection, and then get progress callbacks.
Is there a way to make a synchronous request on that background queue (synchronous in that the job behind it doesn't start until its done), and still get setProgress: callbacks which could be sent to update a progressbar? (Callbacks would have to be on a different queue thread, since my serial queue's thread is blocked until the request is finished.)
Apple's docs for NSURLConnection say that the synchronous request is actually built on top of the asynchronous behind the scenes. Do I have to re-implement that? I need a way to block a thread until the request finishes/fails. The best leads I have so far are NSOperationQueue's waitUntilFinished method, but I don't want to start async and continually poll on the synchronous method.
NSURLConnection Discussion
A synchronous load is built on top of the asynchronous loading code made available by the class. The calling thread is blocked while the asynchronous loading system performs the URL load on a thread spawned specifically for this load request. No special threading or run loop configuration is necessary in the calling thread in order to perform a synchronous load.
Reimplementing a synchronous request on top of an asynchronous request is not that hard. You just need to manually spin the thread's run loop until you see that the request has finished (or failed). Here's a rough example:
NSURLRequest *downloadRequest = [NSURLRequest requestWithURL:queryURL];
// Start the download and wait until it finishes
self.downloadFinished = NO;
self.downloader = [NSURLConnection connectionWithRequest:downloadRequest delegate:self];
while (!self.isDownloadFinished)
{
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
Here are the relevant NSURLConnectionDelegate methods:
- (void)connectionDidFinishLoading:(NSURLConnection *)connection;
{
self.downloadFinished = YES;
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error;
{
NSLog(#"An error occurred: %#", error);
self.receivedData = nil;
self.downloadFinished = YES;
}

better understand of NSURLConnection with NSOperationQueue

I make a queue
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
I send the queue to my async request
[NSURLConnection sendAsynchronousRequest:req queue:operationQueue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
// ... do stuff here
});
}];
I cancel my operations prematurely
[operationQueue cancelAllOperations];
However, I can see my async's "completion" code still running. How come this scenario doesn't work like I expected?
Isn't it your responsibility to check isCancelled during your operation's task in case it gets canceled halfway though?
NSOperationQueue won't just kill tasks, it will set them as cancelled and let them finish themselves. This lets you clean up any resources you might have allocated and tidy up before you exit.
Tasks that haven't started yet won't start.
cancelAllOperations goes through the items in the queue and calls cancel on each one. If you look at the documentation for completionBlock:
The completion block you provide is executed when the value returned by the isFinished method changes to YES. Thus, this block is executed by the operation object after the operation’s primary task is finished or cancelled.
can be seen here
edit:
another snippet from the documentation for setCompletionBlock:
A finished operation may finish either because it was cancelled or because it successfully completed its task. You should take that fact into account when writing your block code. Similarly, you should not make any assumptions about the successful completion of dependent operations, which may themselves have been cancelled.

Resources