0

I am trying to download a set of files from a web server using NSURLConnection but at the point the connection appears to be made, the connection's delegate methods never get fired and so the file never gets downloaded. I have read many answers on SO and other sources and have tried the fixes that have been advised but to no avail, which makes me think I have made a different mistake here.

I have a viewController (InitViewController.m) which loads another class's method:

GetData *getDataInstance = [[GetData alloc] init];
[getDataInstance startUpdate]; 

GetData.m then does some checking and runs the class in charge of getting the files:

GetFiles *getFilesInstance = [[GetFiles alloc] init];
[getFilesInstance doFilesNeedDownloading];

doFilesNeedDowngoading method checks to see if we need the file and then runs getFiles:

-(void)getFile//:(NSString *) fullURL
{

    // I have checked if the connection is run on the main thread and it is
    NSLog(@"Is%@ main thread", ([NSThread isMainThread] ? @"" : @" NOT"));

    NSURL *downloadURL = [NSURL URLWithString:fullURL];

    NSMutableURLRequest *dlRequest = [NSMutableURLRequest requestWithURL:downloadURL];
    NSURLConnection *theConnection = [[NSURLConnection alloc] initWithRequest:dlRequest delegate:self];

    [theConnection scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];

    [theConnection start];

    if(theConnection) { //me checking for connection which is 'true'
        NSLog(@"Connection for %@ worked", fullURL);
    } else {
        NSLog(@"Connection for %@ failed", fullURL);
    }

}
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {

    responseData = [[NSMutableData alloc] init];
    NSString *fileName = [[NSURL URLWithString:fullURL] lastPathComponent];
    NSString *filePath = [[NSSearchPathForDirectoriesInDomains(NSDocumentationDirectory, NSUserDomainMask, YES) objectAtIndex:0]stringByAppendingPathComponent:fileName];
    [[NSFileManager defaultManager] createFileAtPath:filePath contents:nil attributes:nil];
    file = [NSFileHandle fileHandleForUpdatingAtPath:filePath];
    [file seekToEndOfFile];

}
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {

    [responseData appendData:data];
    [file seekToEndOfFile];
    [file writeData:data];

}
-(void)connectionDidFinishLoading:(NSURLConnection *)connection {

    [file closeFile];

}

I did originally fire the getDataInstance startUpdate in a separate thread in an update to have the 'getting data' part of the app separate to the 'UI building' part of the app and thought this might be the issue but for now I have remove that and even put in'[theConnection scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode]' as per other answers to this kind of question on SO.

I'm sure there will be something really obvious that I have missed, any ideas?

Thanks,

EDIT

I have now tried this code again but in the initViewController so this is pretty much the first thing that is fired when the app loads. This is no longer in another class or thread etc.:

-(void)getFile
{
    fullURL = @"http://myURL.com/terms-and-conditions.txt";
    NSURL *downloadURL = [NSURL URLWithString:fullURL];
    NSMutableURLRequest *dlRequest = [NSMutableURLRequest requestWithURL:downloadURL];
    NSURLConnection *theConnection = [[NSURLConnection alloc] initWithRequest:dlRequest delegate:self];

    [theConnection start];
}
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {

    responseData = [[NSMutableData alloc] init];
    NSString *fileName = [[NSURL URLWithString:fullURL] lastPathComponent];
    NSString *filePath = [[NSSearchPathForDirectoriesInDomains(NSDocumentationDirectory, NSUserDomainMask, YES) objectAtIndex:0]stringByAppendingPathComponent:fileName];
    [[NSFileManager defaultManager] createFileAtPath:filePath contents:nil attributes:nil];
    file = [NSFileHandle fileHandleForUpdatingAtPath:filePath];
    [file seekToEndOfFile];   
}
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {

    [responseData appendData:data];
    [file seekToEndOfFile];
    [file writeData:data];
}
-(void)connectionDidFinishLoading:(NSURLConnection *)connection {

    [file closeFile];
}

getFile gets fired but it's delegate methods still don't get fired?

Agamemnon
  • 587
  • 2
  • 15
  • 44
  • 1
    Are you using ARC? If yes, make sure the objects like your GetData and such are not defined weak and get dealloced at at end of the run loop. – Volker Jan 31 '14 at 08:39
  • I put that line in as a potential fix (one that someone suggested elsewhere) removing it doesn't help. – Agamemnon Jan 31 '14 at 08:41
  • @Volker - I am using ARC. GetData is a separate class imported in the .h of initViewController – Agamemnon Jan 31 '14 at 08:45
  • 2
    make sure it is not dealloc'ed too early. You can do this by implementing -(void)dealloc and either put a NSLog there or a breakpoint. – Volker Jan 31 '14 at 08:47
  • 2
    Volker is right; looks like you need to make those separate class instances an instance variable, rather than a local variable. – trojanfoe Jan 31 '14 at 08:49
  • 1
    You are using wrong initializer for `NSURLConnection`. `initWithRequest:delegate:` immediately starts the connection on calling thread. Instead you should use `initWithRequest:delegate:startImmediately:` method and pass `NO` to start immediately part. Then you can schedule connection in another threads run loop. Also @Volker's suggestion should be followed – Amar Jan 31 '14 at 08:49
  • @Volker I'm a bit of Obj-C noob so I'm not sure what I need to change to make them 'separate class instances'? I kind of thought that is what i was doing :S – Agamemnon Jan 31 '14 at 09:01
  • currently you are for example instantiating an object of class GetData in a method of your app delegate (or wherever you call that). So at the end of the method, all objects get released through the autoreleasepool (simplified). Maybe this happens to your object. So, if you want to keep it alive, you have to declare it as a class wide variable or better, as a property (strong). If not sure what all this means, I recommend to start with a book like Aaron Hillegass and learn the basics about OOP in Obj-C and memory management before proceeding. Otherwise you will run into more and more problems. – Volker Jan 31 '14 at 09:07
  • Can you try copy pasting that code to viewController and try to run it? Also change your initializer as Amar did mention it. If that works then we can make future steps for fixing the problem. – Josip B. Jan 31 '14 at 09:17
  • @JosipB. This is a good idea, I have tried it (see question edit) but the method delegates still don't fire. – Agamemnon Jan 31 '14 at 10:30
  • @Volker Apologies, I think I know what you mean now. I have been trying to separate my code out into slightly smaller (more specific) classes that do particular jobs. Are you saying to bring the methods in those classes into the class that calls them and to call them directly rather than instantiating an object of that class? Doesn't that go against the point of OOP? or am I missing the point somewhere (again I'm a bit of a novice so apologies). – Agamemnon Jan 31 '14 at 11:13
  • you're doing the right thing in terms of OOP. Encapsulation is good. Yet, in the beginning, you have to think much more about object lifetimes and how to keep reference on them. Try to make small steps! – Volker Jan 31 '14 at 11:23
  • Did you try using [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO] instead of [[NSURLConnection alloc] initWithRequest:request delegate:self] in initViewController? Here is a great tutorial that may help you better understand how can you use NSURLConnection in your apps: http://codewithchris.com/tutorial-how-to-use-ios-nsurlconnection-by-example/ – Josip B. Jan 31 '14 at 11:34
  • @JosipB. Yeah I tried that and when i step through the code it gets to [theConnection start]; and then goes to the next part of initViewController, skipping the delegate methods. – Agamemnon Jan 31 '14 at 11:42
  • Is your initViewController ConnectionDelegate & ConnectionDataDelegate like ? Did you implement delegate & dataDelegate methods in initViewController class? Implement it like in this class https://github.com/cloverstudio/CSUtils/blob/master/CSUtils/CSMessage/CSMessage.m and it should work. – Josip B. Jan 31 '14 at 11:58
  • @JosipB. I checked initViewController.h and it seems fine: interface InitViewController : ECSlidingViewController but still no joy there. However, I created a new very simple project using your above example to get the connection working outside of my app and it works fine. As soon as I put it into my main app it doesn't, so I will have to investigate why. – Agamemnon Jan 31 '14 at 12:33
  • Then maybe some other components are blocking you since it works in an other project. Let us know once you fix it. – Josip B. Jan 31 '14 at 12:53
  • possible duplicate of [NSURLConnection delegate methods are not called](http://stackoverflow.com/questions/5787170/nsurlconnection-delegate-methods-are-not-called) – 200_success May 28 '15 at 08:23

5 Answers5

1

If you create NSURLConnection in other thread you have to manually start the run loop.

Try with this:

-(void)getFile
{

NSURL *downloadURL = [NSURL URLWithString:fullURL];

NSMutableURLRequest *dlRequest = [NSMutableURLRequest requestWithURL:downloadURL];
NSURLConnection *theConnection = [[NSURLConnection alloc] initWithRequest:dlRequest delegate:self];

[theConnection scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run]; 
[theConnection start];

if(theConnection) { //me checking for connection which is 'true'
    NSLog(@"Connection for %@ worked", fullURL);
} else {
    NSLog(@"Connection for %@ failed", fullURL);
}

}

  • The strange thing here is that with the above code the delegate methods DO fire but they seem to hang? Like it is stuck in an infinite loop or something? RAM stays steady but the app doesn't return to its calling method?? – Agamemnon Jan 31 '14 at 08:55
  • This code perfectly works for me. Can you give more information about the infinite loop issue? – Sri Sooriyakanthan Sivasuthan Jan 31 '14 at 09:29
  • I don't think it is an infinite loop it just seems like it. After connectionDidFinishLoading the app does nothing, Xocde seems like it had finished running and the app is now running on it's own except the app hasn't loaded it's UI or anything. – Agamemnon Jan 31 '14 at 10:45
  • Where did you implement the code for load UI? Is it inside the callback? – Sri Sooriyakanthan Sivasuthan Jan 31 '14 at 10:56
  • Originally, initViewController built the navigation controller and then open a new thread in which a database is built if not built already ([CreateDBInstance createTables];) and then populated via another class ([GetDataInstance startUpdate];). While these are being done on a separate thread the UI is built in another class mainViewController. – Agamemnon Jan 31 '14 at 11:04
  • I guess, this issue is not related with NSURLConnection. Can you give more information about your implementation regarding initializing view controller and updating UI? Sometimes, termination of thread can cause this issue. – Sri Sooriyakanthan Sivasuthan Jan 31 '14 at 13:25
0

I had this problems too when I wanted to start NSURLConnection in a concurrent NSOperation. Performing connection on main thread helped me solve the problem.

- (void)start {

    if (![NSThread isMainThread]) {
        [self performSelectorOnMainThread:@selector(start)
                               withObject:nil
                            waitUntilDone:NO];
        return;
    }
}

Also scheduling sonnection in [NSRunLoop currentRunLoop] helped me to solve the problem:

self.connection = [[NSURLConnection alloc] initWithRequest:request
                                                  delegate:self
                                          startImmediately:NO];
[self.connection scheduleInRunLoop:[NSRunLoop currentRunLoop]
                           forMode:NSRunLoopCommonModes];
[self.connection start];

You can take a look how it's done in CSMessage class that is part of CSUtils framework. Feel free to use given code on your own: https://github.com/cloverstudio/CSUtils

Josip B.
  • 2,434
  • 1
  • 25
  • 30
  • Have just tried this and it still didn't work. I think I may have a larger problem as described in the comments of the question. – Agamemnon Jan 31 '14 at 09:12
0

Put delegate in your class.h like:

@interface InitViewController : UIViewController<NSURLConnectionDelegate,NSURLConnectionDataDelegate>
spongebob
  • 8,370
  • 15
  • 50
  • 83
user3189586
  • 125
  • 1
  • 8
0

In the end I re-wrote the whole class and got the delegates firing.

NSString *currentURL = [NSString stringWithFormat:@"%@/api/sync", apiURL];
NSLog(@"URL = %@", currentURL);
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:currentURL]];
[request addValue:@"application/json" forHTTPHeaderField:(@"Accept")];
NSURLResponse *response = nil;
NSError *error = nil;
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];

Here are the delegates that now fire:

- (void)connection:(FileURLConnection*)connection didReceiveResponse:(NSURLResponse *)response
{
    NSString *fileName = [[response URL] lastPathComponent];
    NSString *filePath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]stringByAppendingPathComponent:fileName];
    [[NSFileManager defaultManager] createFileAtPath:filePath contents:nil attributes:nil];
    connection.file = [NSFileHandle fileHandleForUpdatingAtPath:filePath];
}
- (void)connection:(FileURLConnection *)connection didReceiveData:(NSData *)data
{
    [connection.file writeData:data];
}
- (NSCachedURLResponse *)connection:(FileURLConnection *)connection willCacheResponse:(NSCachedURLResponse*)cachedResponse
{
    return nil;
}
- (void)connectionDidFinishLoading:(FileURLConnection *)connection
{
    [connection.file closeFile];
}
- (void)connection:(FileURLConnection *)connection didFailWithError:(NSError *)error
{
    NSLog(@"GetFiles - didFailWithError - error : %@ for URL %@", error, connection.currentRequest.URL);
}

The class now downloads the file (from my own API) and saves it on the device.

Agamemnon
  • 587
  • 2
  • 15
  • 44
  • How can it calls your delegate if you do not pass it as a parameter? It seems that you are using the Synchronous API, whilst the delegate way is for Asynchronous tasks. – tux_mind Mar 31 '18 at 18:35
  • I believe delegate was set to self but I ommitted this when copy pasting it seems. The delegate methods certainly do fire now. – Agamemnon Apr 06 '18 at 08:06
-1

Just change this line and rest keep as it is in your code. Keep the scheduleInRunLoop line also.

NSURLConnection * connection = [[NSURLConnection alloc] initWithRequest:request
                                                               delegate:self startImmediately:NO];
Funny
  • 566
  • 5
  • 16