Use of NSRunloop for avoiding event driven code - ios

I am using NSUrlConnection for making http requests. I want to avoid event driven code, so I am making use of NSRunloop in the following way:
NSURLRequest *request = [[NSURLRequest alloc]
initWithURL: [NSURL URLWithString:_urlString]
cachePolicy: NSURLRequestReloadIgnoringLocalAndRemoteCacheData
timeoutInterval: 10
];
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
if(!connection)
{
DebugLog(#"Creating a connection has failed");
[self setValidationRequestResult:false];
}
else
{
NSRunLoop* currentRunLoop = [NSRunLoop currentRunLoop];
[self.connection start];
while (self.isConnectionComplete == NO)
{
NSDate* dateLimit = [NSDate dateWithTimeIntervalSinceNow:0.05];
[currentRunLoop runUntilDate:dateLimit];
}
//TODO: check release of NSUrlConnection
}
}
// Now perform remaining tasks ........
Is it okay to NSRunLoop in way shown above or should we post notifications in "didFailWithError" and "connectionDidFinishLoading" to write logic after the http request is done?

While that approach may be technically correct, I would encourage you to ask why you want to do this.
NSURLConnection was designed to attach itself to a runloop so the runloop can continue and not tie up that thread.
What I've done in my applications is have a class dedicated to managing my networking code and have delegate callbacks or pass in blocks to handle completion. You mentioned using Notifications and that is also a good idea.
This is the nature of asynchronous networking code. It makes the code a bit more complicated, but your application will be better off.

Related

NSURLConnection started in another thread. Delegate methods not called

I start a NSURLConnection in another thread:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0),
^{
NSURLConnection *connection = [NSURLConnection connectionWithRequest:[request preparedURLRequest] delegate:self];
[connection start];
});
But my delegate method is not called:
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData*)data;
When run on the main thread everything is fine. How can I run connection on another thread and get the delegate methods called at the same thread too?
GCD creates, destroys, reuses threads implicitly and there is a chance that the thread you call start from will stop existing immediately afterwards. This may result in the delegate not receiving any callbacks.
If you would like to receive callback in background thread, you can use setDelegateQueue or sendAsynchronousRequest:queue:completionHandler: method:
NSURLConnection* connection = [[NSURLConnection alloc] initWithRequest:request
delegate:self
startImmediately:NO];
[connection setDelegateQueue:[[NSOperationQueue alloc] init]];
[connection start];
The easiest way to start NSURLConnection in the background thread via GCD is:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
^{
NSURLResponse* response = nil;
NSError* error = nil;
[NSURLConnection sendSynchronousRequest:request] returningResponse:&response error:&error];
NSLog(#"%#", response);
});
Yes, this is well known behavior of NSURLConnection because it needs a run loop to process the delegate events. The most common solution is (a) instantiate it with initWithRequest:delegate:startImmediately: where startImmediately is FALSE; (b) manually scheduleInRunLoop:forMode: to schedule it in the main run loop; and then (c) start the connection.
But, as you have it here, there's no point in dispatching this to a background queue, as it's already asynchronous so you should just initiate this from the main queue and none of the above is necessary. You use the above pattern in special cases (e.g. you were using NSOperation subclass to manage your requests), but generally it's not needed.
Also, FYI, effective iOS9, NSURLConnection is deprecated, so you should be using NSURLSession, anyway. And NSURLSession doesn’t suffer this limitation.
I had a similar issue. What I'm doing now is running NSURLConnection request in the main thread - it is running asynchronously so it won't slow down your application. In connectionDidFinishLoading, I run the following code to process the results of my calls. I perform the check because I have NSURLConnection call which may trigger other network calls. Since they are already running on a background thread I don't want to start a new one.
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
if ([NSThread isMainThread]) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
//Background Thread
[self processFinishLoading:connection];
});
}
else {
[self processFinishLoading:connection];
}
}

Multiple NSURLConnection sendAsynchronous requests with a singleton class

I am using a Master Detail Controller. In the Master list there are 5 items. On selecting each item, there are Asynchronous calls made.
There is one SingleTon class, which handles all network calls.
[[MyNetwokCommunication connectionInstance]
makeRequest:"url1" delegate:self];
[[MyNetwokCommunication connectionInstance] makeRequest:"url2" delegate:self];
Actual implementation in makeRequest:delegate: is [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediatley:YES].
So, is this a correct way to handle network connection with a singleton class, as it might over-ride data part of one request with another.
There are a lot of ways to handle it. Unfortunately, the fact that NSURLConnection can't be used as a key in a dictionary makes life particularly miserable.
The best thing to do, assuming you don't still need to support iOS 6 and earlier (or OS X v10.8 or earlier) is to ditch NSURLConnection for NSURLSession. Then use block-based calls to make the request and handle the response. Unlike NSURLConnection objects, you can use NSURLSessionTask objects as dictionary keys, which makes it easy to keep track of what data came from which request, and to store additional objects or other data associated with that request (e.g. storing the UI cell where the data should go).
If you have to use NSURLConnection to support older operating systems, you might consider subclassing NSURLRequest and adding extra data. Note that this alternative approach does not work with NSURLSession, and you'll need to provide your own redirect handler, because otherwise you'll end up getting back generic NSURLRequest objects whenever a redirect happens, IIRC. Or you can add Objective-C associated objects on the connection object. (At least I'm 99% sure that this works.)
That's not how I would do it.
The best paradigm on iOS to serialize things is an NSOperationQueue. You can create a queue with concurrency of 1, then queue your NSURLConnection or NSURLSession children as NSOperations.
This allows you to do neat things like create operations that are dependent on the success of other operations.
Here's the creation of the queue:
#property (strong, nonatomic) NSOperationQueue *queue;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_queue = [[NSOperationQueue alloc] init];
[_queue setMaxConcurrentOperationCount:1];
});
Then to create a network operation:
// HTTPOperation.h
#import <Foundation/Foundation.h>
#interface HTTPOperation : NSOperation
#end
//
// HTTPOperation.m
//
#import "HTTPOperation.h"
#implementation HTTPOperation
- (void) main
{
NSURLRequest * urlRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:#"http://google.com"]];
NSURLResponse * response = nil;
NSError * error = nil;
NSData * data = [NSURLConnection sendSynchronousRequest:urlRequest
returningResponse:&response
error:&error];
if (error == nil)
{
// Parse data here
NSLog(#"Completed successfully");
}
}
#end
To execute the operations:
- (IBAction)queueHTTP {
HTTPOperation *op = [HTTPOperation new];
[self.queue addOperation:op];
}
You can queue as many as you want from anywhere and they will execute serially. I recommend watching the WWDC video on Advanced NSOperations:
https://developer.apple.com/videos/wwdc/2015/?id=226
It was very informative.

Error in using asynhronous request in iOS%? [duplicate]

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!

iOS: sendSynchronousRequest with performSelectorInBackground

I need to make several https calls to a certain url. Therefore I do something like this
//ViewController Source
-(IBAction) updateButton_tapped {
[self performSelectorInBackground:#selector(updateStuff) withObject:nil];
}
-(void) updateStuff {
// do other stuff here...
NSMutableURLRequest* request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:self.url]];
[request setHTTPMethod:#"POST"];
NSData *postData = [[Base64 encodeBase64WithData:payload] dataUsingEncoding:NSASCIIStringEncoding];
[request setHTTPBody:postData];
NSURLResponse* response = [[NSURLResponse alloc] init];
NSData* data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:nil];
//Process the recieved data...
//Setup another synchronous request
data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:nil];
//Process data
//do this another 4 times (note for loop cannot be use in my case ;) )
//Finally update some view controllers
[[NSNotificationCenter defaultCenter] postNotificationName:#"NotificationIdentifier" object:self];
}
So the problem with this code is that it crashes randomly (not always but frequently). I get no debugging output on the log. Sometime my whole app freezes or it simply crashes the whole program. But it never crashes if I run it on the main thread. Therefore I think the code is correct and I suppose now that it has something to do with the threading on the iphone.
What problems could happen when running the code this way and what might cause a random crashes?
Memory Management, you don't release your request or response objects after allocation.
Please re-check your code so you don't update any GUI in background thread. Also, it should be much better to use asynchronous processing.
The controllers or whatever are probably assuming they're receiving notifications on the main thread, which in your case they're not (and that's never a safe assumption to make). Have the controllers dispatch back to the main thread in their notification callbacks before they do anything with the data/updating the UIKit stuff, etc.
You should also put an #autorelease block around your entire implementation of -updateStuff.
Here's an example of a callback notification you might receive in one of your controllers:
- (void)updateStuffNotificaiton:(NSNotification*)note
{
// Can't assume we're on the main thread and no need to
// test since this is made async by performSelectorInBacground anyway
dispatch_async(dispatch_get_main_queue(), ^{
// relocate all your original method implementation here
});
}
Also note that if your implementation of -updateStuff is creating and manipulating data structures that your notification callback methods then access, it is important to properly guard those accessors. It's often better to pass the data wholesale back to the callbacks in the notification's userInfo dictionary.
An example of adding the autorelease notation to your -updateStuff method:
-(void) updateStuff
{
#autoreleasepool {
// do other stuff here...
NSMutableURLRequest* request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:self.url]];
[request setHTTPMethod:#"POST"];
// rest of method snipped for brevity
//Finally update some view controllers
[[NSNotificationCenter defaultCenter] postNotificationName:#"NotificationIdentifier" object:self];
}
}

iOS, NSURLConnection: Delegate Callbacks on Different Thread?

How can I get NSURLConnection to call it's delegate methods from a different thread instead of the main thread. I'm trying to mess around with the scheduleInRunLoop:forMode:but doesn't seem to do what I want.
I have to download a large file and it interrupts the main thread so frequently that some rendering that is happening starts getting choppy.
NSURLRequest * request = [NSURLRequest requestWithURL:url];
NSURLConnection * connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO];
NSRunLoop * loop = [NSRunLoop currentRunLoop];
NSLog(#"loop mode: %#",[loop currentMode]);
[connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
[connection start];
The other thing I don't see much of is "Modes" There are only two modes documented so not much really to test with.
Any ideas?
Thanks
There are several options:
In your implementation of the delegate methods, make use of dispatch_async.
Start the schedule the connection on a background thread.
You can do the latter like this:
// all the setup comes here
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSRunLoop *loop = [NSRunLoop currentRunLoop];
[connection scheduleInRunLoop:loop forMode:NSRunLoopCommonModes];
[loop run]; // make sure that you have a running run-loop.
});
If you want a guarantee on which thread you're running, replace the call to dispatch_get_global_queue() appropriately.
If you want to perform downloads on a separate thread, I'm pretty sure these are the droids you're looking for...
- (void) dispatchRequest{
self->finished = NO;
NSMutableURLRequest* request = //Formulate your request
NSThread* download_thread = [[NSThread alloc] initWithTarget:self selector:#selector(downloadThreadLoop:) object:request];
[download_thread start];
}
- (void) downloadThreadLoop:(NSMutableURLRequest*) request{
NSURLConnection* connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
while(!self->finished]){
//This line below is the magic!
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
//...
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
//...
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{
//...
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection{
//...
self->finished = YES;
}
If you truly need to do the download in a new thread, it may be easier to detachNewThreadSelector:toTarget:withObject:, setup (and destroy) an NSAutoreleasePool, and then use one of the synchronous selectors like NSData's dataWithContentsOfURL:. This will not make use of the asynchronous NSURLConnectionDelegate.
Because this call is synchronous, it will not return until the file has been downloaded, which will block the main thread, but because you're in a new thread, it won't. Please note that this is typically discouraged behavior. Is there other code happening in the main thread that can be optimized?
NSURLConnection is already doing the download off of the main thread asynchronously. If I understand your question, you would like the delegate messages to be sent on a thread other than the main thread? You can't do that as you can't modify the internal implementation of NSURLConnection. I can think of two ways to simulate this.
Create a sublcass of NSURLConnection (e.g. MyURLConnection) that assigns itself as own delegate. Note that this creates an intentional retain cycle so be careful. MyURLConnection should define a new delegate that supports NSURLConnectionDelegate. Let's call this finalDelegate. When MyURLConnection handles it's own delegate messages, forward or dispatch them to finalDelegate on whatever thread you like.
Similar to option #1 but without the subclass. Handle the NSURLConnection delegate methods on the main thread and forward/dispatch them to whatever thread you like.
The main difference is if you want a reusable subclass that behaves this way or it's a one off implementation.
EDIT: added suggestion on how to run code in the background
If you are going to process the response in the background I would either use operations or grand central dispatch. No need to mess around with run loops and creating threads. Check out Apple's Concurrency Programming Guide.

Resources