NSXMLParser initWithContentsOfURL timeout - iphone

How can I set a timeout when I am parsing a feed using initWithContentsOfURL. Sometimes our feeds return a blank response and then it will just try to parse the feed forever. I want to set a timeout of 30 seconds or so that will pop up a UIAlertView and then try and reparse the feed.
NSURL *feedURL = [[NSURL alloc] initWithString:URL];
NSXMLParser *parser = [[NSXMLParser alloc] initWithContentsOfURL:feedURL];
[parser setDelegate:self];
[parser setShouldProcessNamespaces:NO];
[parser setShouldReportNamespacePrefixes:NO];
[parser setShouldResolveExternalEntities:NO];
[parser parse];

First approach: using a delayed selector
Probably the simplest way to do this is to use NSObject's performSelector:withObject:afterDelay: method. You can define some method parsingDidTimeout as such:
- (void)parsingDidTimeout {
if(self.parsingDidComplete == NO) {
[self.parser abortParsing];
// Create your error and display it here
// Try the fetch and parse again...
}
}
This requires that you hang on to the parser as an instance variable (self.parser), so that you can cancel it from the method you define. It also requires that your parser delegate keep track of whether or not the parser has finished (self.parsingDidComplete, can be defaulted to NO and set to YES in the delegate's parserDidEndDocument: method). This is to avoid aborting a successful parse. After this is done, all it takes is a simple
[self performSelector:#selector(parsingDidTimeout) withObject:nil afterDelay:30];
and thirty seconds later, your parsing-abort code will get called, and you can do whatever it is you need to do.
Second approach: using a timer
You could make this whole approach (arguably) simpler in the timeout method by using an NSTimer instead of the NSObject method call. That way, if the parser successfully finishes, you can simply invalidate the timer, allowing you to eliminate the if clause in the parsingDidTimeout method (and, as a result, also getting rid of the BOOL ivar). The timer initialization would look like:
NSTimer *timer = [NSTimer timerWithTimeInterval:30.0
target:self
selector:#selector(parsingDidTimeout)
userInfo:nil
repeats:NO];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];

Doesn't answer your question direction, but you'd have complete control over the request and response cycle (as well as asynchronicity without additional threads) if you used NSURLConnection to download the data. That's what initWithContentsOfURL: is doing, under the covers.

Swift 3 example using NSTimer aka Timer
func startParseTimeoutTimer() {
Timer.scheduledTimer(withTimeInterval: 2.0, repeats: false) { (_) in
if (self.busy) {
self.parser?.abortParsing()
self.parser = nil
print("Show UI as NOT busy; we aborted for timeout \(Thread.current.isMainThread)")
}
}
}

Related

NSURLRequest Timeout IOS

I need to set timeout 15sec or 30 sec with UIRequest, but it always takes default one. Is there any way to set minimum timeout to connection.
This answer explains about the minimum value of timeoutInterval of an NSURLRequest object. If you need a smaller value, then you may do so with starting an NSTimer with the desired time and in the firing method of the timer, you cancel the connection of your NSURLConnection object. As in:
//....
connection = [[NSURLConnection connectionWithRequest:request delegate:self] retain];
[request release];
[connection start];
if (timer == NULL) {
timer = [NSTimer scheduledTimerWithTimeInterval: TimeOutSecond
target: self
selector: #selector(cancelURLConnection:)
userInfo: nil
repeats: NO];
[timer retain];
}
- (void)cancelURLConnection:(NSTimer *)timerP {
[connection cancel]; //NSURLConnection object
NSLog(#"Connection timeout.");
[timer invalidate];
}
There seems to be a problem with setting the timeout interval property at construction time:
NSMutableURLRequest* request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:url] cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:240.0];
Instead set it AFTER construction:
request.timeoutInterval = 70;
Also note that there seem to be some limitations to how low you can set the interval. Read this post for more information:
https://devforums.apple.com/message/108087#108087
POST requests have a timeout minimum which is 4 minutes, I believe. The most secure way is to start a NSTimer and cancel the request when the timeout fires.

FTP file using an async method call in iPhone app

I am new to iPhone app development. I want to have a async method that will be called on successful login and work async while I am navigating to various views in the app.
This method should independently work without affecting the main view methods. This method is performing ftp of the files on the local folder to the server.
Could you please tell me or put some sample code which I can refer to. I want to see both for ftp and async method processes.
from my understanding you want to upload something from the iphone to a server in a background thread?
anyway; downloading in a background thread should be quite similar.
first, i suggest you create a method that does the main work for you:
- (void) createRessource {
NSURL *destinationDirURL = [NSURL URLWithString: completePathToTheFileYouWantToUpload];
CFWriteStreamRef writeStreamRef = CFWriteStreamCreateWithFTPURL(NULL, (__bridge CFURLRef) destinationDirURL);
ftpStream = (__bridge_transfer NSOutputStream *) writeStreamRef;
BOOL success = [ftpStream setProperty: yourFTPUser forKey: (id)kCFStreamPropertyFTPUserName];
if (success) {
NSLog(#"\tsuccessfully set the user name");
}
success = [ftpStream setProperty: passwdForYourFTPUser forKey: (id)kCFStreamPropertyFTPPassword];
if (success) {
NSLog(#"\tsuccessfully set the password");
}
ftpStream.delegate = self;
[ftpStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
// open stream
[ftpStream open];
}
This method is one third of the job: it will be called in the background.
Invoked from something like this:
- (void) backgroundTask {
NSError *error;
done = FALSE;
/*
only 'prepares' the stream for upload
- doesn't actually upload anything until the runloop of this background thread is run!
*/
[self createRessource];
NSRunLoop *currentRunLoop = [NSRunLoop currentRunLoop];
do {
if(![currentRunLoop runMode: NSDefaultRunLoopMode beforeDate: [NSDate distantFuture]]) {
// log error if the runloop invocation failed
error = [[NSError alloc] initWithDomain: #"org.yourDomain.FTPUpload"
code: 23
userInfo: nil];
}
} while (!done && !error);
// close stream, remove from runloop
[ftpStream close];
[ftpStream removeFromRunLoop: [NSRunLoop currentRunLoop] forMode: NSDefaultRunLoopMode];
if (error) {
// handle error
}
/* if you want to upload more: put the above code in a lopp or upload the next ressource here or something like that */
}
Now you could call
[self performSelectorInBackground: #selector(backgroundTask) withObject: nil];
and a background thread will be created for you, the stream will be scheduled in its runloop and the runloop is configured and started.
Most important is the starting of the runloop in the background thread - without it, the stream implementation will never start working...
mostly taken from here, where i had a similar task to perform:
upload files in background via ftp on iphone

performSelectorOnMainThread throws message deallocated

While parsing of the twitter data, I used threads to call the main URL to download the data. It does the downloading perfectly but when I hit the back button while the data is downloading it throws performSelectorOnMainThread message deallocated. I know we can use isCancelled but its not working for me yet. Does anyone have come across this issue and have resolved it.
- (void)LoadTwitterData
{
NSString *urlString =#"http://search.twitter.com/search.json?q=tabc&result_type=recent&rpp=2500";
NSURL *url = [NSURL URLWithString:urlString];
NSString *jsonString = [NSString stringWithContentsOfURL:url];
NSDictionary *values = [jsonString JSONValue];
/**** Throws here *****/
[self performSelectorOnMainThread:#selector(didFinishLoadingResults:) withObject:values waitUntilDone:NO];
}
If you spin off a thread using a selector on self, you need to make sure that self is retained for the duration of that thread, otherwise (as in your case) self can be deallocated and your thread will try to call back into a zombie. The easiest way to do this is to pass self to the thread as an argument. If you use performSelectorInBackground:withObject: you should do something like this:
[self performSelectorInBackground:#selector(LoadTwitterData) withObject:self];
Or if you use NSThread you should pass self to the object: initializer argument.
In fact the safest way to use thread methods is to make the method static like this:
+ (void)LoadTwitterData:(id)arg
{
// ...
MyController *self = arg;
// ... do work
[self performSelectorOnMainThread:#selector(didFinishLoadingResults:)
withObject:values waitUntilDone:NO];
}
This way you are unable to access instance variables by accident which avoids various multi-threading issues. Any and all data the thread needs, including the self to callback to, should be passed in as 'arg', which can be an array or dictionary or whatever you need. This way you know that everything the thread needs will be retained for the duration of the thread, and because you aren't accessing instance variables through self, another thread can't go and change them around underneath you.
Another thing you should do in a thread method is wrap the whole thing with an autorelease pool:
- (void)LoadTwitterData
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
#try {
// ...
} #finally {
[pool drain];
}
}
If LoadTwitterData: is in a background thread, you need to create an Auto release pool (If you haven't already). Surround your code with-
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
//your code
[pool drain];

NSXMLParser avoids UILabel to show/hide

I have the following methods in my class:
-(IBAction)loginToAccount:(id)sender {
// Display the network activity status indicator
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
// Show the load indicator
[self.loadIndicator startAnimating];
self.loadIndicator.hidden = NO;
self.loadLabel.hidden = NO;
[usernameTextField resignFirstResponder];
[passwordTextField resignFirstResponder];
[self CheckLoginCredentials];
}
-(void)CheckLoginCredentials {
NSString *APIURL = [[NSString alloc] initWithFormat:#"http://mysite.com/xml.xml"];
NSURL *url = [[NSURL alloc] initWithString:APIURL];
NSXMLParser *xmlParser = [[NSXMLParser alloc] initWithContentsOfURL:url];
[APIURL release];
[url release];
[xmlParser setDelegate:self];
[xmlParser parse];
}
When I comment [self CheckLoginCredentials], the loadIndicator gets animated and shown but when I uncomment [self CheckLoginCredentials], the loadIndicator does not get shown and also usernameTextField/passwordTextField resignFirstResponder do no work.
What am I doing wrong? Thanks!
I believe that -initWithContentsOfURL: is a synchronous url connection, and therefore blocks the thread it's run on until it completes.
Because of this, the progress indicator won't get shown because it requires that the thread it's running on has an active run loop. Using the synchronous url connection on the main thread will block the UI on that thread, so you won't see your progress indicator.
The correct way to do this would be to use NSURLConnection's
+ (NSURLConnection *)connectionWithRequest:(NSURLRequest *)request delegate:(id)delegate
Simply create an NSURLRequest object that encapsulates your API request, and then pass it off to that method on NSURLConnection.
Then implement the delegate call backs to get your data back.
The advantage of this method is that all of this is done on a separate thread and handled for you, and therefore won't block your UI.

NSURLConnection delegation and threading - iPhone

I have a class that updates two .plist files in the app documents directory via an NSURLConnection. The class acts as its own delegate for NSURLConnection. It works properly when I ask for a single file, but fails when I try to update two files. Does it look like I should start a new thread for each of the getNewDatabase messages?
- (void)getAllNewDatabases {
[self performSelectorOnMainThread:#selector(getNewDatabase:) withObject:#"file1" waitUntilDone:YES];
[self performSelectorOnMainThread:#selector(getNewDatabase:) withObject:#"file2" waitUntilDone:YES];
}
- (BOOL)getNewDatabase:(NSString *)dbName
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSMutableString *apiString = [[NSMutableString alloc] initWithString:kAPIHost];
[apiString appendFormat:#"/%#.plist",dbName];
NSURLRequest *myRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:apiString] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:60.0];
NSURLConnection *myConnection = [[NSURLConnection alloc] initWithRequest:myRequest delegate:self];
[apiString release];
if( myConnection )
{
//omitted for clarity here
}
[pool release];
}
//NSURLConnection delegate methods here ...
I found something interesting with NSURLConnection and NSThread - the thread will only live as long as it takes to perform the method that you call from it.
In the case above the thread will live only as long as getNewDatabase:(NSString *)dbName takes to complete, therefore killing off any of its delegate methods before they actually have time to do anything.
I found this website that gives a better explanation and a solution to the problem
I tweaked it a little bit so I could have a custom time out if it didn't complete in a given time frame (handy when someone is walking around between access points)
start = [NSDate dateWithTimeIntervalSinceNow:3];
while(!isFinished && [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
beforeDate:[NSDate distantFuture]]){
if([start compare:[NSDate date]] == NSOrderedAscending){
isFinished = YES;
}
}
As it stands currently in the code you provided, getNewDatabase: is running on the main thread of your application. The problem in this particular case then is something other than the life cycle of the thread, as James observed in his case.
If you did intend to perform this operation in the background, I'd recommend looking into using NSOperationQueue and NSOperation rather than solving the problem with the current code. I think your case is a great fit for NSOperationQueue, especially given that you have more than one download task to perform.
Dave Dribin has an excellent article about using asynchronous API, such as NSURLConnection, inside an NSOperation. Alternatively, as long as you're running in a background thread, you can also simplify the process and just use a synchronous API method instead in your NSOperation, such as initWithContentsOfURL:.
Marcus Zarra has also written a tutorial that demonstrates how easy it is to incorporate and use NSOperationQueue for simple background operations.