How to force close NSURLConnection maunally? - iphone

I am using a NSURLConnection to make an http download. Depending upon the condition I may have to drop the connection after a specific download. Say, if my download is completed in 30 seconds then ok other wise download should be stopped after 30 seconds. Also I have to log these events. My problem is that, even after 30 seconds it keeps on downloading data and the events are logged only after download complete.
In simple words I want to force close the download and also want to disable all the delegate events that are fired by http connection. I do not want to set flags at multiple locations, that would complicate things further. Any Ideas?
Update: Complete scenario:
I have set the timeoutInterval to 10 seconds in NSURLReqeust object. Now what happens if no data is received for 10 seconds the connection is automatically drops after 10 seconds works perfectly fine. But I have another feature in my software that requires to terminate the connection with download in not completed in given amount of time. I am using a separate NSTimer. All I can do is set a flag when NSTimer event is fired. Now in case the flag is set via NSTimer and data stops coming in, I have no connection delegate that would be fired for next 10 seconds. Now my problem is both the abort event and timeout events occurs at the same time.

Well, you "can" cancel a NSURLConnection by sending it a cancel event:
[connection cancel];
See Apple docs.
Prior to that just nil out the delegate and you should not be harassed by any delegate callbacks.

Use NSURLRequest object to specify a timeout for evrey request you did for download by using this requestWithURL:cachePolicy:timeoutInterval: method.
Please check whether your NSURLConnection's delegate is set and responds to the connection:didFailWithError: method. A NSURLConnection calls either this method or connectionDidFinishLoading: upon connection completion.
Handle 'didFailWithError' method and check the reason for failer by using NSError object.
But if you get response from server and response time is slow, then used NSTimer.
Create Helper class for downloading data so you can reuse the class for multiple downloads by creating several instances and set NSTimer in it, if download finish within 30 seconds invalidate timer else cancel downloading [self.connection cancel].
Please check following code:
- (void)_startReceive
// Starts a connection to download the current URL.
{
BOOL success;
NSURL * url;
NSURLRequest * request;
// Open a connection for the URL.
request = [NSURLRequest requestWithURL:url];
assert(request != nil);
self.connection = [NSURLConnection connectionWithRequest:request delegate:self];
assert(self.connection != nil);
// If we've been told to use an early timeout for get complete response within 30 sec,
// set that up now.
self.earlyTimeoutTimer = [NSTimer scheduledTimerWithTimeInterval:30.0 target:self selector:#selector(_earlyTimeout:) userInfo:nil repeats:NO];
}
}
- (void)_stopReceiveWithStatus:(NSString *)statusString
// Shuts down the connection and displays the result (statusString == nil)
// or the error status (otherwise).
{
if (self.earlyTimeoutTimer != nil) {
[self.earlyTimeoutTimer invalidate];
self.earlyTimeoutTimer = nil;
}
if (self.connection != nil) {
[self.connection cancel];
self.connection = nil;
}
}
- (void)_earlyTimeout:(NSTimer *)timer
// Called by a timer (if the download is not finish)
{
[self _stopReceiveWithStatus:#"Early Timeout"];
}
- (void)connection:(NSURLConnection *)conn didReceiveResponse:(NSURLResponse *)response
// A delegate method called by the NSURLConnection when the request/response
// exchange is complete.
{ }
- (void)connection:(NSURLConnection *)conn didReceiveData:(NSData *)data
// A delegate method called by the NSURLConnection as data arrives. We just
// write the data to the file.
{ }
- (void)connection:(NSURLConnection *)conn didFailWithError:(NSError *)error
// A delegate method called by the NSURLConnection if the connection fails.
{
NSLog(#"didFailWithError %#", error);
// stop Receive With Status Connection failed
}
- (void)connectionDidFinishLoading:(NSURLConnection *)conn
// A delegate method called by the NSURLConnection when the connection has been
// done successfully. We shut down the connection with a nil status.
{
NSLog(#"connectionDidFinishLoading");
// If control reach here before timer invalidate then save the data and invalidate the timer
if (self.earlyTimeoutTimer != nil) {
[self.earlyTimeoutTimer invalidate];
self.earlyTimeoutTimer = nil;
}
}

Related

Can I stop or cancel loop function when using [NSThread sleepForTimeInterval:2.0]; in IOS

I have a loop function and in it called [NSThread sleepForTimeInterval:2.0];. it mean after 2s, loop function is called. I want when pass new view, this loop function is stop and when back, it is called again.
I use this code to call loop function:
-(void) viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
loop = YES;
delete=NO;
temp = [FileCompletedArray mutableCopy];
NSOperationQueue *queue = [NSOperationQueue new];
operations = [[NSInvocationOperation alloc] initWithTarget:self selector:#selector(updateArray) object:nil];
[queue addOperation:operations];
[operations release];
}
And loop function:
-(void)updateArray{
while (loop)
{
NSLog(#"start loop");
if(loop){
[NSThread sleepForTimeInterval:2.0];
NSLog(#"start send request");
NSURL *url1 = [NSURL URLWithString:#"http://server.com"];
NSMutableURLRequest *afRequest = [httpClient requestWithMethod:#"POST" path:nil parameters:params1] ;
operation= [[AFHTTPRequestOperation alloc] initWithRequest:afRequest];
NSLog(#" request sent");
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"Server response1");
}
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"error: %#", error);
}
];
[httpClient enqueueHTTPRequestOperation:operation];
}
else
return;
}
}
And viewdisappear()
-(void) viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
loop = NO;
delete=NO;
[operations cancel] ;
}
My problem is when pass new view, updateArray still call. It not stop. Do you have suggestion?
You can try it using key value observers. You can implement the changes in following method which will be automatically called as a particular value changes. First you have to set the notification for the ivar that is going to be changed.
[self addObserver:self forKeyPath:#"loop" options:NSKeyValueObservingOptionNew context:nil];
Then you have to implement the changes as per requirement in the following method:
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
There are a couple of issues that leap out at me:
If your intent is to actually cancel the sleepForTimeInterval, I don't believe you can do that. There are other mechanisms (such as timers) that are much better suited for that problem.
As an aside, you are issuing an asynchronous request every two seconds. But you have no assurances that your previous request will have completed in that period of time, though. As a result, you can end up with a backlog of multiple network requests that will continue to run after the view is dismissed.
I would have thought that you'd want to initiate the "wait two seconds" inside the completion block of your asynchronous request, to ensure your requests don't get backlogged behind your "every two seconds" logic. Clearly that won't work with your current while loop unless you make the request synchronous, so you might refactor this code, replacing the while loop with something that performs a single request, and in the completion block, waits two seconds before initiating the next request.
You are checking the state of loop, waiting two seconds, and then issuing your request. So if the view disappeared while it was performing the two second sleep, there's nothing here stopping the request from being issued after you finished sleeping.
At the very least, if you're going to use sleepForTimeInterval, you presumably want to check loop state after you finish sleeping. But, to my first point, it's better to use some cancelable mechanism, such as a timer.
If you're saying that your loop never exits, I'd suggest you check to make sure the appearance methods are getting called like you think they should be.
Personally, I'd be inclined to do this with a timer which can easily be canceled/invalidated:
Define my properties:
#property (nonatomic, strong) NSTimer *timer;
#property (nonatomic, getter = isLooping) BOOL looping;
#property (nonatomic, weak) AFHTTPRequestOperation *operation;
Have my appearance methods set the looping variable and start/stop the scheduled requests as appropriate:
- (void)viewDidAppear:(BOOL)animated
{
self.looping = YES;
[self scheduleRequestIfLooping];
}
- (void)viewWillDisappear:(BOOL)animated
{
self.looping = NO;
[self cancelScheduledRequest];
}
The methods that do the starting and stopping of the scheduled requests would use the NSTimer:
- (void)scheduleRequestIfLooping
{
if ([self isLooping]) {
self.timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:#selector(initiateRequest:) userInfo:nil repeats:NO];
}
}
- (void)cancelScheduledRequest
{
[self.timer invalidate];
self.timer = nil;
// you might want to cancel any `AFHTTPRequestOperation`
// currently in progress, too
[self.operation cancel];
}
Note, whether the cancel method should cancel both the timer and any current request in progress (if any) is up to you.
Finally, put the scheduling of the next request inside the completion block of the current request.
- (void)initiateRequest:(NSTimer *)timer
{
// create AFHTTPRequestOperation
AFHTTPRequestOperation operation = ...
// now schedule next request _after_ this one, by initiating that request in the completion block
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"Server response1");
[self scheduleRequestIfLooping];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"error: %#", error);
// not sure if you want to schedule request if last one failed
}];
self.operation = operation; // save this in the property
}
In the above, I'm using the main thread for the timer. If you really want to do it on a background queue, you can, but (a) it seems unnecessary to me as the network requests already happen on a background thread; and (b) if you do this on a background thread, you might want to make sure you're doing the necessary thread-safe handling of your state variables and the like. It just seemed like an unnecessary complication to me.

Wait for URLConnection block to finish

I'm creating a REST client class for my iPad app. So I created a BOOL method which does the login using an NSURLConnection subclass I created earlier.
This JWURLConnection has block type properties for the finishLoading and failWithError operations.
The Problem is that the URL connection most likely finishes (or fails) AFTER this method is completely executed. A cannot use an extra method to use performSelector:waitUntilDone: too because I have to wait for the connection.
Now I tried using plain C semaphores and an extra thread (so that the semaphore blocks only the RESTClient thread, not the URLConnections one), but I had no success; the method started waiting but the whole connection stuff was frozen, thus there where no NSLogs from the connection.
The JWURLConnection starts it's own thread by itself within the -start method:
- (void)start { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [super start]; }); }
Here is the code I tried it with (using semaphores):
- (BOOL)loginWithUsername:(NSString *)uName ansPassword:(NSString *)pWord {
__block BOOL loginSucceeded = NO;
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
JWURLConnection *connection = [JWURLConnection connectionWithPOSTRequestToURL:POSTData:];
[connection setFinished^(NSData *data) {
// validate server response and set login variable
loginSucceeded = YES;
dispatch_semaphore_signal(sema);
}];
[connection setFailed:^(NSError *error) {
loginSucceeded = NO;
NSLog(#"Login failed: %#", [error description]);
dispatch_semaphore_signal(sema);
}];
[connection start];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
// do some more stuff like error handling / reporting here
return loginSucceeded;
}
I hope you can lead my the right direction...
The JWURLConnection starts it's own thread by itself within the -start method:
- (void)start { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [super start]; }); }
You need to ensure that a NSURLConnection's delegate methods will be scheduled on a NSRunLoop or a NSOperationQueue. While the start method could actually take care of this - the given code and your comment indicate it does not ;) In short, dispatch_async does not guarantee that the underlaying thread has a run loop and a dispatch queue does not even guarantee that the underlaying thread is always the same.
The docs show how to schedule a connection.
I would suggest to schedule the connection on the main thread, and change this to a NSOperationQueue when required.
Your loginWithUsername:andPassword: method will simply return immediately since you call/invoke an asynchronous function/method.
Employing asynchronous patterns is kinda "infectious". Once you started using asynchronous programming style, you cant get "rid of" it unless you use synchronization primitives that block the current thread. I would suggest to keep the async style:
- (void) loginWithUsername:(NSString *)uName
andPassword:(NSString *)pWord
completion:(void(^)(id result))onCompletion;
And later:
[self loginWithUsername:#"Me" andPassword:#"secret" completion:^(id result) {
if ([result != [isKindOfError class]]) {
[self fetchImagesWithURL:url completion: ^(id result) {
...
}];
}
}];

Long Polling and applicationDidEnterBackground:

I'm writing a very Simple Chat Application and would like to know how to suspend the long polling selector when the Application enters background.
Currently, I have a Chatroom class (A UIView) which handles the long polling like so:
-(void)startPolling
{
[self performSelectorInBackground:#selector(longPoll) withObject: nil];
}
- (void) longPoll {
//Poll the Requested URL...
NSData* responseData = [NSURLConnection sendSynchronousRequest:request
returningResponse:&response error:&error];
[self performSelectorOnMainThread:#selector(dataReceived:)
withObject:responseData waitUntilDone:YES];
[self performSelectorInBackground:#selector(longPoll) withObject: nil];
}
-(void) dataReceived: (NSData*) data
{
//Reload my Tableview etc..
}
How do I use applicationDidEnterBackground: to suspend the longPoll selector until the application comes back to the foreground? Or is this automatically done by the Application Delegate?
The request will automatically be suspended. It's not guaranteed that the request will necessarily succeed after being resumed, so you'll have to handle errors, but it shouldn't break.
Note that there are probably better ways to write this than using performSelectorInBackground:, which always spins up a new hardware thread. For starters, it's probably better to simply loop inside longPoll instead of starting a new thread for the new request.

Setting timeout on a delegate method in objective c

i need to set a NSTimer object to manually timeout a server call if it is taking more than 10 seconds (not supported in Restkit)
This is my code below. Essentially, my loader class will delegate the request with loadObjectsAtResourcePath
If it takes more than 10 seconds, I would like to call the same failure method that Restkit calls when it hits an error with the server (didFailWithError)
But i feel that i am doing it wrong, and furthermore, the failure method requires a object which is only initialized in the delegate class.
//CLASS FOR LOADING OBJECTS
-(void)getObjects{
RKObjectManager *sharedManager = [RKObjectManager sharedManager];
// loads the object via delegate
[sharedManager loadObjectsAtResourcePath:self.resourcePath delegate:self];
//creates an error
NSError *error = [NSError errorWithDomain:#"world" code:200 userInfo:nil];
// Setting timeout here. goto failure
nTimer = [NSTimer scheduledTimerWithTimeInterval:10.0f target:self.delegate selector:#selector:(objectLoader:nil didFailWithError:error:) userInfo:nil repeats:NO];
}
// handles failure
- (void)objectLoader:(RKObjectLoader*)objectLoader didFailWithError:(NSError*)error {
..
}
// handles success
- (void)objectLoader:(RKObjectLoader*)objectLoader didLoadObjects:(NSArray*)objects {
..
}
What is the right way to do this?
It would be better to handle the timeout in a method other than the delegate callback. As you say, the delegate method requires objects that are created within the class. And you probably don't want to handle a "real error" and a timeout in exactly the same way, right? With a timeout you might, for example, want the option of trying again.
If you did want the timeout and failing with an error to do exactly the same thing, you can stil use another method for this:
-(void)getObjects{
RKObjectManager *sharedManager = [RKObjectManager sharedManager];
// loads the object via delegate
[sharedManager loadObjectsAtResourcePath:self.resourcePath delegate:self];
//creates an error
NSError *error = [NSError errorWithDomain:#"world" code:200 userInfo:nil];
// Setting timeout here. goto failure
nTimer = [NSTimer scheduledTimerWithTimeInterval:10.0f target:self selector:#selector(didTimeout) userInfo:nil repeats:NO];
}
- (void)didTimeout {
NSLog(#"Error");
}
// handles failure
- (void)objectLoader:(RKObjectLoader*)objectLoader didFailWithError:(NSError*)error {
[self didTimeout];
}
// handles success
- (void)objectLoader:(RKObjectLoader*)objectLoader didLoadObjects:(NSArray*)objects {
//don't forget to invalidate the time or else you'll get errors even when successful
[nTimer invalidate];
}
You can of course extend this to be more flexible if necessary, but this seems to cover what you asked.
Not familiar with RestKit but the first thing I think of is that it would be far better to set the scheduled selector to call a verfication method rather than the did fail method, have the verification method check to see if a valid response has been received within the defined time and if it hasn't then cancel the request etc and call the fail.
Whilst I am not familiar with RestKit I am familiar with using NSURLRequests and I know it's possible to define a timeout when you issue the request to generate a timeout failure - don't know if that helps...

how to stop performing selector in background?

I have some class A. In this class i have a method,
which calls [self performSelectorInBackground:...]. And it starts downloading
some info from internet.
After i tap Home button, then enter the app again, this background method keeps working.
So, if i call this method again, i have bad_access, because background method is already working and i call it twice.
Can i stop performing selector in background of the class A? For example in my applicationDidEnterBackground?
Or can i check, if selector is performing or something?
I found couple things like
[[NSRunLoop currentRunLoop] cancelPerformSelectorsWithTarget:a];
[NSObject cancelPreviousPerformRequestsWithTarget:a selector:#selector(startDownload) object:nil];
But they didn't work for me.
So
my objAppDelegate:
#inteface ObjAppDelegate
{
A *a;
}
#implementation ObjAppDelegate
{
-(void)applicationDidEnterBackground:(UIApplication *)application
{
//or it can be didBecomeActive..
//here. check if background task of class A is running, or just stop it ??
}
}
#implementation A
{
//some timer, or event, etc.
-(void)startDownload
{
[self performSelectorInBackground:#selector(runBackgroundTask) withObject:nil];
}
-(void)runBackgroundTask
{
//some network stuff..
}
}
i did it like this:
threadForDownload = [[NSThread alloc] initWithTarget:self selector:#selector(threadMain:) object:nil];
[threadForDownload start];
[self performSelector:#selector(startDownload) onThread:threadForDownload withObject:nil waitUntilDone:NO];
(void)threadMain:(id)data {
NSAutoreleasePool *pool = [NSAutoreleasePool new];
NSRunLoop *runloop = [NSRunLoop currentRunLoop];
[runloop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
while (YES) {
[runloop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
[pool release];
}
In my startDownload method i look at activity indicator to check, whether
startDownload is already running..
-(void)startDownload
{
if (![[UIApplication sharedApplication] isNetworkActivityIndicatorVisible]) // flag..
{
//....
}
}
// I make visible networkActivityIndicator every time i start downloading
You can easily create a BOOL instance variable to determine whether background task is active.
BOOL isBackgroundTaskRunning;
Then in runBackgroundTask
if (isBackgroundTaskRunning) {
// already running
return;
}
isBackgroundTaskRunning = TRUE;
...
isBackgroundTaskRunning = FALSE;
Here's what to do:
the background task saves its thread to a property somewhere using NSThread currentThread
the background task periodically checks the thread's isCancelled property.
the main thread sends cancel to the thread object saved by the background thread in step 1.
On exit, the background thread sets the property to nil.
All of the operations on the property used to store the thread in have to be protected by #synchronized or equivalent to prevent the main thread from sending cancel to a deallocated thread object.
The background thread can't do IO operations that block for more than a short period of time. In particular, synchronous downloading of URLs using NSURLConnection is out. If you are using NSURLConnection, you'll want to move to the asynchronous methods and a run loop (arguably, in that case, you can do away with the background thread altogether). If you are using POSIX level IO, use poll() with a timeout.
I don't think that it would be save to force the interruption of a method. What you can do is to change the state of your object and check that state inside your method implementation to early return in case of a cancel (but don't forget to release allocated objects).
This is how NSOperationQueue works. From the documentation:
Cancelling an operation does not immediately force it to stop what it is doing. Although respecting the value returned by the isCancelled is expected of all operations, your code must explicitly check the value returned by this method and abort as needed.
Run the method in a background thread, and keep a record of the NSThread. Then later, you can just end the thread.