Making threaded NSURLConnections - ios

I have a lot of connections going when my app starts, so I wanna put them on background threads so I can make new connections other than the starting connections before they all complete.
Below, threadedRequest: is a method that's starting a NSURLConnection, but when I call performSelectorInBackground:withObject: in the if clause, the connection starts, but never finishes. The else clause works fine and returns data from the connection
if (background)
{
[self performSelectorInBackground: #selector(threadedRequest:) withObject: args];
}
else
{
[self performSelector: #selector(threadedRequest:) onThread: [NSThread mainThread] withObject: args waitUntilDone: NO];
}

If you want to perform NSURLConnection in the background, you must be sensitive to special conditions that apply to NSURLConnectionDataDelegate methods in background threads. You have several options:
Use one of the non-delegate alternatives. Given that you're performing this in the background, you could use sendSynchronousRequest, or if just requesting data from a simple URL, you could use NSData class method dataWithURL.
If you really need the delegate version (because you need progress updates via didReceiveData or because you need one of the NSURLConnectDelegate methods for authentication or the like, you have a couple of basic options:
You can create a NSOperationQueue and set the delegate queue for the connection. For example, rather than:
- (void)startConnection:(NSURL *)url
{
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[NSURLConnection connectionWithRequest:request delegate:self];
}
You could do:
- (void)startConnection:(NSURL *)url
{
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO];
[connection setDelegateQueue:self.connectionDelegateQueue];
[connection start];
}
Alternatively, you could do what AFNetworking does (see AFURLConnectionOperation.m, for sample implementation):
Create a dedicated NSThread for the NSURLConnectionDataDelegate calls;
Start a NSRunLoop on that thread;
Use the same startImmediately:NO rendition of the NSURLConnection init method as above; and
Use the scheduleInRunLoop option for the NSURLConnection before you start it.
Or, easiest, you could just use AFNetworking
Two observations:
I might be inclined to use operation or dispatch queues instead of performSelectorInBackground. See Concurrency Programming Guide.
By the way, you should be aware that there is a limit as to how many concurrent NSURLConnection requests iOS can perform simultaneously (it's 5 or 6, I believe). Thus, if you initiate a dozen background requests and then initiate a "foreground" request, the "foreground" request may not start immediately. You may want to limit how many background requests that can operate concurrently if you want to avoid this potential problem.
Personally, when I want to constrain the number of network requests, I add my background requests to a NSOperationQueue and set the number of maximum concurrent operations (e.g. I generally use 4), enjoying the benefits of concurrent operations, while not trying to perform more simultaneous connections than iOS will permit. By the way, when availing yourself of operation queue's maxConcurrentOperations feature, you have to make sure that the requests are synchronous (or wrap the asynchronous connection in a custom NSOperation that won't terminate until the connection is done or fails).

Related

Fifth NSURLConnection's delegate not called

I am using NSURLConnection in my app for downloading. It is working fine.When i start the connection the delegates methods
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSHTTPURLResponse*)response
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
- (void)connectionDidFinishLoading:(NSURLConnection*)connection
get called.
But when the number of connections become more then 4 then for the 5th connection the delegate methods do not called until the delegate method "connectionDidFinishLoading" is called for any of first four connections (means one of any four connection has finished). Then the delegates (didReceiveResponse and didReceiveData ) of Fifth connection start getting called .In short my NSURLConnection is delegate methods only called for 4 connections at a time.
I just want to know is there any way that i can increase this limit so that more then 4 (may be 8 or 9) connection's delegates get called at a time ? Is this a limit by iOS or something is wrong with my app.
Check http://blog.lightstreamer.com/2013/01/on-ios-url-connection-parallelism-and.html
iOS has a limit on the number of concurrent NSURLConnections to
the same end-point? No? Maybe you should, we discovered it the hard way.
They also offer a thread pool implementation to get around this limit.
I think you should schedule these connections to a manual created queue other than the default one, so as to avoid the potential affect. It works well for me. Here are the sample code you can try:
NSURLRequest *request; // a URL request
NSOperationQueue *queue = [[NSOperationQueue alloc] init]; // manual create queue to schedule your URL connections
queue.maxConcurrentOperationCount = 10; // configure the max count
// by specifying NO for the #startImmediately# parameter,
// you can schedule an connection on the queue you like before it starts.
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO];
[connection setDelegateQueue:queue];
[connection start];
Hope this will be helpful.
You can't fix it with NSURLConnection (practically), but if you use NSURLSession instead, you can trivially solve the problem.
The NSURLSession API lets you control the maximum number of concurrent requests per server. Just set the HTTPMaximumConnectionsPerHost property on the session configuration object, then create a session based on that configuration, then create tasks within that session much like you would create NSURLConnection objects.

AFHttpRequestOperation inside NSOperation

Been doing iOS for a while, but now I'm working in a complex client-server app that requires me to:
Send a request to the server (using AFHttpRequestOperation)
The response is either an interval or a Dictionary
If I get an interval, I need to wait (interval)-seconds and call again the server again (wait / sleep?)
this continues until I receive an expired message, the user cancels or a Dictionary (as mentioned above) is received.
IF a dictionary is received, I have to pause the calling to the server until the user takes an action on the information I display to him.
Based on user decision, I need to resume and keep calling, wait, calling, and so on until i do receive the dictionary or it expires (if the user rejected the information) OR if the user accepts, stop asking and further process the information (but I wont keep calling the serve)
Being new to threading in iOS, what's the best approach:? I have been working with dispatch_asynch + performSelector:onThread:withObject but it's giving me all sort of problems!
Question is: What's the best approach / approach to achieve pause, cancel, wait / sleep in an NSOpertation subclass that internally uses an AFHttpRequestOperation?
I'm a rookie at threading, specially under iOS, so your help is greatly appreciated!!!!
I would suggest not to use threading with networking . While it doesn’t seem clear enough but implementing a asynchronous network requests take very less CPU time.
Many try to do this and end up with delegates methods of an asynchronous network calls not being called. If network calls are performed on a background thread, the thread might probably exit before the delegates are finished calling.
The asynchronous calls can be cancelled just by calling the cancel/pause method. If you feel that main thread is very slow, make sure you avoid heavy operation on it, like parsing, calling CameraView. I used NSrunLoop for just testing how to do this, without much advantages
-(void)start
{
[self willChangeValueForKey:#"isExecuting"];
_isExecuting = YES;
[self didChangeValueForKey:#"isExecuting"];
NSURL* url = [[NSURL alloc] initWithString:#"http:your url"];
NSMutableURLRequest* request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:30];
connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO]; // ivar
[request release];
[url release];
// Via the run loop
NSPort* port = [NSPort port];
NSRunLoop* runLoop = [NSRunLoop currentRunLoop];
[runLoop addPort:port forMode:NSDefaultRunLoopMode];
[connection scheduleInRunLoop: runLoop forMode:NSDefaultRunLoopMode];
[connection start];
[runLoop run];
}
You can replace the native api's with your library ones. Hope this helps in some way.

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
}
}

Using NSURLConnection Multiple times

I am trying to start a second NSURLConnection after starting the first one. My first one works perfectly: the appropriate delegates are all called, and everything executes as planned. However, after the first one finishes, I create a second one, and nothing happens (the delegate is never called). What could be happening? I know I can't reuse the same NSURLConnection, but I reinitialise it before using it again, so it should be a completely new connection.
Here's my code for starting (both) connections. It's the same instance variable, but it's reinitialised. Also note that the second one is not started until the first one has finished running completely.
if (connection) {
[connection cancel];
}
currentResponse = nil;
error = nil;
connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO];
if (!connection) {
NSLog(#"Connection could not be initialized.");
[self connectionFinished];
} else {
[connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[connection start];
}
You haven't shared how you're running it "on a different thread", but the typical problem is if you are using dispatch queues or operation queues, your connection is running asynchronously, itself, and therefore the dispatched operation is completing and getting released and you're losing your connection.
A couple of possible solutions:
You can perform your network operations synchronously in this background operation of yours (this is the only time you should do synchronous network operations). This is the simplest solution, though you haven't explained what you're doing with your NSURLConnection, so this technique may or may not work for you. But if you're just trying to download something from a URL, you can do:
NSData *data = [NSData dataWithContentsOfURL:url
options:0
error:&error];
This approach doesn't work if you're doing any a little more complicated that requires the NSURLConnectionDataDelegate methods, such challenge-response authentication or if you are streaming using didReceiveData to reduce your app's memory footprint or for performances reasons, etc. But if you're just trying to download data from a remote server (e.g. retrieving an image, an XML/JSON feed, etc.), this is easiest.
In a similar vein (i.e. you don't need the NSURLConnectionDataDelegate methods), but you're you're creating some rich NSURLRequest for your connection, then you can use either sendAsynchronousRequest or sendSynchronousRequest.
If you need the NSURLConnectionDataDelegate calls, you can use the setDelegateQueue (designating a NSOperationQueue) or scheduleInRunLoop (designating a NSRunLoop), and it will automatically dispatch the connection updates to the appropriate queue/runloop. Just make sure to initWithRequest using the startImmediately option of NO, set the delegate queue or runloop, and then start the connection. With this technique, you preserve the full richness of NSURLConnectionDataDelegate if you absolutely need it.
Alternatively, if you're not using operation queues, you could also keep your background operation alive until the connection is done. This approach gives you synchronous behavior in you background operation (keeping your connection alive), while preserving the NSURLConnectionDataDelegate methods. This technique is demonstrated by Apple's XMLPerformance Sample (see the downloadAndParse method of CocoaXMLParser.m and LibXMLParser.m), where they initiate the NSURLConnection and then use the following construct to keep the background operation alive until the NSURLConnectionDataDelegate methods end up setting the done instance variable:
do {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
beforeDate:[NSDate distantFuture]];
} while (!done);
I confess that I find this final approach vaguely dissatisfying and would lean towards the other alternatives, depending upon what flexibility and functionality you need from your NSURLConnection. For us to provide more meaningful counsel, you just need to provide more information about (a) the sort of work you're doing in your NSURLConnectionDataDelegate methods; and (b) which technology you're using to run your code in the background.
For a few more options, also feel free to see GCD and async NSURLConnection.

Queuing NSURLRequest to simulate a synchronous, blocking request

I am interacting with a web-controlled hardware device. You send it a request via a URL (e.g., http://device/on?port=1 or http://device/off?port=3) to turn stuff on and off, and it sends back "success" or "failure". It is a simple device, however, so while it's processing a request --- i.e., until it returns the status of the request that it's processing --- it will ignore all subsequent requests. It does not queue them up; they just get lost.
So I need to send serial, synchronous requests. I.e., req#1, wait for response#1, req#2, wait for response#2, req#3, wait for response #3, etc.
Do I need to manage my own thread-safe queue of requests, have the UI thread push requests into one end of the queue, and have another thread pull the requests off, one at a time, as soon as the previous one either completes or times out, and send the results back to the UI thread? Or am I missing something in the API that already does this?
Thanks!
...R
What should work is to use an NSOperationQueue instance, and a number of NSOperation instances that perform the various URL requests.
First, set up a queue in the class that will be enqueueing the requests. Make sure to keep a strong reference to it, i.e.
#interface MyEnqueingClass ()
#property (nonatomic, strong) NSOperationQueue *operationQueue;
#end
Somewhere in the implementation, say the init method:
_operationQueue = [[NSOperationQueue alloc] init];
_operationQueue.maxConcurrentOperationCount = 1;
You want basically a serial queue, hence the maxConcurrentOperationCount of 1.
After setting this up, you'll want to write some code like this:
[self.operationQueue addOperationWithBlock:^{
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:#"my://URLString"]];
NSError *error;
NSURLResponse *response;
NSData *responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
if (!responseData)
{
//Maybe try this request again instead of completely restarting? Depends on your application.
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
//Do something here to handle the error - maybe you need to cancel all the enqueued operations and start again?
[self.operationQueue cancelAllOperations];
[self startOver];
}];
}
else
{
//Handle the success case;
}
}];
[self.operationQueue addOperationWithBlock:^{
//Make another request, according to the next instuctions?
}];
In this way you send synchronous NSURLRequests and can handle the error conditions, including by bailing out completely and starting all over (the lines with -cancelAllOperations called). These requests will be executed one after the other.
You can also of course write custom NSOperation subclasses and enqueuing instances of those rather than using blocks, if that serves you.
Hope this helps, let me know if you have any questions!
You can use NSOperationQueue class and also use some API which are build in on it for example AFNetworking.

Resources