iOS: UIWebViewDelegate didFailLoadWithError not called when connection dropped -1005 error - ios

I have a problem loading a large web page in a UIWebView where the connection drops for some reason trying to load a java script file. The load stalls at the point with neither didFailLoadWithError or webViewDidFinishLoad ever being invoked. The delegate is setup correct as when there is no networking error the page loads fine and the webViewDidFinishLoad method is invoked.
While trying to figure out why the page load stalls I implemented and registered a custom NSURLProtocol where I found out that connection delegate didFailWithError would be invoked most of the time, but the web view delegate would never be invoked after that.
I changed the custom protocol to run the connection in the main thread, which caused the didFailWithError method to be invoked every time the network connection dropped
- (void)startLoading {
NSMutableURLRequest *newRequest = [self.request mutableCopy];
[NSURLProtocol setProperty:#YES forKey:ProtocolKey inRequest:newRequest];
self.connection = [[NSURLConnection alloc]
initWithRequest:newRequest
delegate:self startImmediately:NO];
[self.connection scheduleInRunLoop:[NSRunLoop mainRunLoop]
forMode:NSDefaultRunLoopMode];
[self.connection start];
}
The web view delegate webViewDidFinishLoad method was still not being called so I added a notification to the didFailWithError method, which allows me to handle the error in my web view controller
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
NSDictionary *dict = #{ #"errorDescription":error.localizedDescription };
[[NSNotificationCenter defaultCenter] postNotificationName:ErrorKey object:nil ];
[self.client URLProtocol:self didFailWithError:error];
}
I can't figure out why it seems that the request being run by the web view on the background thread seem to get destroyed before the delegate methods can be invoked and the errors handled?
I was having similar issues with WKWebView, I assume with the same resource javascript, but I can not trap the errors in the custom protocol handler so I don't know what is going on there.
Has any one run into this before? Or have a better way of resolving this problem?

Related

Getting error in async calls if the View Controller is no longer exists

I have a remote service that i call and it processes the request asynchronously. When the data is returned, i'll refresh my local UI.
But sometimes when the View disappears and if asynchronous call is still in the queue then the app crashes with error EXEC BAD ACCESS (i.e. the object is already released) i.e.
My app crashes when the service returns but the ViewController is disposed.
Mainly i am getting error when calling [delegate respondsToSelector:#selector (methodName:)], after the the view controller is no longer exist.
May be i need to cancel all my asynchronous calls (running or waiting in queue) in viewWillDisappear. But i am not able to cancel the running calls.
I have already tried this but in viewWillDisappear my self.navigationController.delegate is already nil.
Edit:
Method to call service:
{
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:[NSURL URLWithString:[NSString stringWithFormat:#“%#method_name”,Base_URL]]];
[request setRequestMethod:#"POST"];
[request setTimeOutSeconds:600];
[request setPostValue:userID forKey:#“id”];
[request setDelegate:self];
[request setDidFinishSelector:#selector(requestFinished:)];
[request setDidFailSelector:#selector(requestFailed:)];
[request startAsynchronous];
}];
[operationQueue addOperation:operation];
}
And my requestFinished method (where my app crashes)
-(void)requestFinished:(ASIHTTPRequest *)request
{
// some stuff
// It's working fine when I normally run my app but fails when I rapidly changes the View Controller.
if ([delegate respondsToSelector:#selector(gotResponseData:)]) // Here my app crashes
{
[delegate gotResponseData:responseDict];
}
}
Delegate property in .h file:
#property (nonatomic,assign)id <protocolName>delegate;
Mainly this app crashes when I quickly switches between View Controller.
I'll edit my question if needed.
Kindly provide me some guidance.
In your block operation you should use a weak reference to self so that if/when self is released the operation doesn't call an invalid reference.
Indeed, you shouldn't actually need the operation as you are starting an asynchronous process inside the operation anyway.
Your delegate property in self should also be weak as your code indicates the problem is that the delegate is using an unsafe unretained approach (because of where you say the crash occurs).
Generally you would only have one request running from self so you should maintain a reference to the request and cancel it if self is destroyed.

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!

UITableView partial loading

hi I have an UITableView. It loads numberof data from a web service. What I want to load this tableview 10 by 10.Initially it loads first 10 items. When user scroll to the end of the UITableView it should load next 10 of records from the server. so in my scrollviewDidEndDeclarating delegate I put like this
`
if (scrollView.tag==24) {
[self performSelector:#selector(loadingalbumsongs:) withObject:nil afterDelay:0.1];
}`
but the problem is when I stop the scroll it is getting stuck untill load the table view. Can anybody give me a solution for this
Thanks
Try NSURLCONNECTION that will help you to call asynchronous webservice
A NSURLConnection object is used to perform the execution of a web service using HTTP.
When using NSURLConnection, requests are made in asynchronous form. This mean that you don't wait the end of the request to continue,
This delegate must have to implement the following methods :
connection:didReceiveResponse : called after the connection is made successfully and before receiving any data. Can be called more than one time in case of redirection.
connection:didReceiveData : called for each bloc of data.
connectionDidFinishLoading : called only one time upon the completion of the request, if no error.
connection:didFailWithError : called on error.
EXAMPLE: -
NSData *data = [[NSMutableData alloc] init];
NSURL *url_string = [NSURL URLWithString:
#"Your URL"];
NSURLRequest *request = [NSURLRequest requestWithURL:url_string];
NSURLConnection *conn = [[NSURLConnection alloc] initWithRequest:request
delegate:self];
if (!conn) {
// this is better if you #throw an exception here
NSLog(#"error while starting the connection");
[data release];
}
for each block of raw data received you can append your data here in this method :
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)someData {
[data appendData:someData];
}
connectionDidFinishLoading will call at the end of successfully data receivied
use this code for load more action
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
//scrollView.contentSize.height-scrollView.frame.size.height indicates UItableView scrool end
if (scrollView.contentOffset.y >= scrollView.contentSize.height-scrollView.frame.size.height)
{
if(loadMore)
{
loadmore=no;
//call your Web service
}
}
}

NSUrlConnection When Lock the Screen?

I have tried some ways to perform NSURLConnection when lock the screen but none of it works.
I have tried as following:
[self performSelectorInBackground:#selector(startConnection) withObject:nil];
I also tried:
dispatch_queue_t request_queue = dispatch_queue_create("com.app.download", NULL);
dispatch_async(request_queue, ^{
[self startConnection];
});
in startConnection:
- (void)startConnection{
... some URL processing
responseData_ = [[NSMutableData alloc] init];
connection_ =
[[NSURLConnection alloc] initWithRequest:urlRequest delegate:self];
}
The NSURLConnection delegate methods aren't called by this way.
What is the real code to make it works? Thanks!
A small update that may help:
It only calls this delegate method:
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
with message:
A server with the specified hostname could not be found.
I am very sure my wi-fi is connected, still not sure why it is called :(
If you lock your screen, your app will be turn into background mode, not background running mode.
If you want to download while user locks the screen, you should check this method [UIApplication -beginBackgroundTaskWithExpirationHandler:]

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