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...
Related
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;
}
}
I need to make a call getValuesAndCalculate in my app, which should return only after doing its work. However, for doing its work, it needs to get records from a server, which has to be done through an async call. The server data is received through a callback function. Thus, in getValuesAndCalculate I need to wait till I have the data before proceeding with the calculations. How do I implement this?
Use protocols and delegates :
delegates are nothing but you assigning an object from your class to a object on the server side .
The server can use this object to call a method on your client side code.
Try using NSRunloop till you get the data from the server.
For eg :
while (!isFinished)
{
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:1.0]
}
You can implement Threads for this question i.e,
// in main thread
// start activity indicator
NSThread *calculateThread = [[NSThread alloc] initWithTarget:self selector:#selector(getValuesAndCalculate) object:nil];
[calculateThread start];
// End of Main thread
- (void)getValuesAndCalculate
{
// Perform transactions with server
[self performSelectorOnMainThread:#selector(continueMainThreadOperations) withObject:nil waitUntilDone:YES];
}
Thats it!
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.
I use AsyncSocket on the iPhone to communicate with a server. AsyncSocket is based on run loops but my app is based on threads. That means, I start a new thread to write data and wait until a response is received on the same thread. But I can't call an AsyncSocket's method directly from another thread, I have to use:
[self performSelectorOnMainThread:#selector(writeSomeData:) withObject:dataToWrite waitUntilDone:YES];
It does work, but I cannot get the response from my method "writeSomeData:" called this way, because performSelectorOnMainThread returns nothing.
The method writeSomeData: does something like this:
-(NSData *)writeData:(NSData *)dataToWrite {
dataReceived = nil; // AsyncSocket writes data to this variable
[asyncSocket writeData:dataToWrite withTimeout:-1 tag:0];
[asyncSocket readDataToData:[#"<EOF" dataUsingEncoding:NSUTF8StringEncoding] withTimeout:-1 tag:0];
int counter = 0;
while (dataReceived == nil && counter < 5) {
// runLoop is [NSRunLoop currentRunloop]
[runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.3]];
++counter;
}
return [dataReceived copy];
}
I could get the response by accessing the class variable "dataReceived", but it's content is changed at this time.
Can anybody tell me how to use AsyncSocket (or generally, how to deal with run loop based classes) on separate threads, so that if I call a method of that class it blocks until the method is executed and a response is received?
Thank you.
Try using GCD(Grand Central Dispatch) to write your data on a separate thread and than come back to the main thread the moment that the data was written. You could do it like this:
// call this on the main thread
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSData *data = [self writeData:dataToWrite];
dispatch_async(dispatch_get_main_queue(), ^{
// do something with the data on the main thread.
});
});
I hope something like this can help you...
Long time lurker, first time poster.
I'm making a ServerConnection module to make it a whole lot modular and easier but am having trouble getting the delegate called. I've seen a few more questions like this but none of the answers fixed my problem.
ServerConnection is set up as a protocol. So a ServerConnection object is created in Login.m which makes the call to the server and then add delegate methods in Login to handle if there's an error or if it's done, these are called by ServerConnection like below.
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
if( [self.delegate respondsToSelector:#selector(connectionDidFinish:)]) {
NSLog(#"DOES RESPOND");
[self.delegate connectionDidFinish:self];
} else {
NSLog(#"DOES NOT RESPOND");
}
self.connection = nil;
self.receivedData = nil;
}
It always "does not respond". I've tried the CFRunLoop trick (below) but it still doesn't work.
- (IBAction)processLogin:(id)sender {
// Hide the keyboard
[sender resignFirstResponder];
// Start new thread
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
// Acutally call the server
[self authenticate];
// Prevent the thread from exploding before we've got the data
CFRunLoopRun();
// End thread
[pool release];
}
I copied the Apple URLCache demo pretty heavily and have compared them both many times but can't find any discrepancies.
Any help would be greatly appreciated.
Here are the questions to ask:
Does your delegate respond to connectionDidFinishLoading:?
Does the signature match, i.e. it takes another object?
Is the delegate set at all or is it nil? (Check this in that very method)
If any of those are "NO", you will see "doesn't respond"... and all equally likely to happen in your application, but all are easy to figure out.