First of all, I don't think that this question already exists, but I agree there is similar posts, please keep reading.
My question is: how abort a NSUrlConnection in his "is connecting" state? I mean, after the connection is made, we can use NSUrlConnection cancel method to cancel requests. But how abort it in "connecting" state, before it reaches the timeout when the server doesn't provide a response (before receiving any delegate calls)?
Thanks for your time!
EDIT
Should I use NSURLSession​Task instead of NSUrlConnection to do that (with its method cancel)?
EDIT 2 - code sample
NSURLConnection* m_connection;
m_connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO];
if(m_connection){
[m_connection start];
m_timer = [NSTimer scheduledTimerWithTimeInterval: FLT_MAX
target: self selector: #selector(doNothing:)
userInfo: nil repeats:YES];
m_runLoop = [NSRunLoop currentRunLoop];
[m_runLoop addTimer:m_timer forMode:NSDefaultRunLoopMode];
while (m_bRunLoop && [m_runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);
[m_connection cancel];
}
I use my connection to stream data. As you can see, for now I abort my connection setting m_bRunLoop to false and it's ok. But my question is : how abort my connection before the server send an response, without wait for the entire timeout?
You can still call [NSURLConnection cancel] to cancel connection and have no further delegate calls. Remember that you have to create a new connection object if you want to reconnect. From your question I deduce rather that you have a problem how to make this cancel call before receiving any delegate calls, is that the case?
Also, consider using NSURLSession API with data tasks as this is probably better way to handle networking in most cases.
EDIT (as you added code):
First of all, note that adding a timer to your run loop doesn't change anything in here as NSTimer is not considered as input source for the run loop (if you really "do nothing").
Second - if you set m_bRunLoop to false you do it somewhere, though you didn't provide the code - but this will be the place to cancel your connection, so let's name this place "cancelConnection" method.
Modify your code as follows:
m_connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO];
if(m_connection){
yourConnectionThread = [NSThread currentThread]; // store your thread in instance variable
[m_connection start];
m_timer = [NSTimer scheduledTimerWithTimeInterval: FLT_MAX
target: self selector: #selector(doNothing:)
userInfo: nil repeats:YES];
m_runLoop = [NSRunLoop currentRunLoop];
[m_runLoop addTimer:m_timer forMode:NSDefaultRunLoopMode];
}
and implement method where you cancel connection, remember that you need to call cancel on a thread on which you started connection:
- (void)cancelConnection {
//m_bRunLoop = false <- this was before here
[m_connection performSelector:#selector(cancel) onThread:yourConnectionThread withObject:nil waitUntilDone:YES];
}
Last, but rather as a comment - remember that NSRunLoop is not thread safe and you should not call it's methods from different threads.
Related
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];
}
}
I'm trying to run a download in a background thread as to not block the main UI thread on iOS, what I did was create a new thread using
[NSThread detachNewThreadSelector:#selector(startDownload) toTarget:downloadObject withObject:nil];
Then the following code runs on a background thread:
NSURL* urlForCalendar = [NSURL URLWithString:#"http://www.apple.com/"];
urlRequest = [NSURLRequest requestWithURL:urlForCalendar];
urlConnection = [[NSURLConnection alloc] initWithRequest:urlRequest delegate:self startImmediately:NO];
NSRunLoop* runLoop = [NSRunLoop currentRunLoop];
[urlConnection scheduleInRunLoop:runLoop forMode:NSRunLoopCommonModes];
[urlConnection start];
However, the delegate callbacks are never called.
EDIT:
For anyone who might come across a similar problem in the future, after a bit of trying to figure out why it wasn't working, I wasn't running the loop. So the last 3 lines of code should actually be:
NSRunLoop* runLoop = [NSRunLoop currentRunLoop];
[urlConnection scheduleInRunLoop:runLoop forMode:NSRunLoopCommonModes];
[urlConnection start];
[runLoop run];
You don't run the run loop of the thread you created, so the connection you add to the run loop is never serviced and you never get any callbacks.
Generally you just want to handle the callbacks on the main thread and then push the result to a background thread if heavy processing is required.
You can do what you're currently doing though so long as you run the run loop and tidy up properly once the download is complete.
I've been looking at this issue for a while now and can't seem to find the solution anywhere on google and the ios documentation.
My app sends 3 asynch requests. The first two are called from the main thread, and the last is called from another thread, in this order. They are sent one after the other but with relatively little delay between them.
//build the request url
NSURL *defaultResultsURL = [self getDefaultResultsURL];
//create the request
NSMutableURLRequest *request =
[NSMutableURLRequest requestWithURL:defaultResultsURL
cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
timeoutInterval:MAX_TIMEOUT_BACKEND];
//invoke requestAllDefaultResults for all elements - invoke on main thread
defualtsResponse = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO];
[defualtsResponse scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
[defualtsResponse start];
The problem is that sometimes, not all the times and only on one of the iPhones I'm testing, and only when the app is first installed, the first request returns with timeout error, in less than 5 seconds, which is the specified request timeout.
I've checked the backend server, and I see that all three requests are received and responses are sent back.
I did try to check if the current thread is running before sending the requests using
if([NSThread isMainThread]){
defualtsResponse = [[NSURLConnection alloc] initWithRequest:request delegate:self];
}
But I'm still getting timeout errors.
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.
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.