4

I have an app that allow automatic login for a user. What my boss want is, when the app launches, the app need to test connectivity to our server (not just test whether there is WiFi or 3G network). I borrowed apple's Reachability sample, it works.

A problem is that takes too long, especially at the launch time. I tried it on a local wireless network without internet connection, it took me almost half an minute to do so. And since auto login called in -ViewDidLoad(), for that half minute, ui didn't load. It is just simply not acceptable to take the time that long. What's even worse, if my app takes too long to load, iOS might even shut the app down.

Further more, I have a lot of web service calls in my app. I understand each web service call could have the chance to fail, because the nature that iPhone/iPad can lose or gain connection easily even when user talk a short walk from one place to another. I personally don't like it but that's what my boss want me to do, and I probably have to test the connection quite often in the app.

So here what I am looking for is either a way to detect connection really really fast(within seconds) or a way to do it behind the scenes and not effect user experience.

Anybody has suggestions?

Thank you for taking your time reading my questions.

Raymond Wang
  • 1,484
  • 2
  • 18
  • 33
  • Perhaps it is a server issue? I'm using similar reachability code in my app, and it works perfectly. When the user clicks login I fire off a checkServerAvailability method and if it returns false, displays an alert. All this happens in a flash - never more than a fraction of a second.. – anon_dev1234 Jul 11 '12 at 16:59
  • Do you actually have the internet disconnected or the device doesn't have any connectivity(say you turn off the wifi on the device)? What I used for testing is a wireless router without an internet connection. So the device thinks it connects to wifi. If I run Reachability sample, it will show me that localWiFi and TCP/IP Routing both available. – Raymond Wang Jul 11 '12 at 17:07
  • From the document: A remote host is considered reachable when a data packet, sent by an application into the network stack, can leave the local device. Reachability does not guarantee that the data packet will actually be received by the host. – funct7 Jan 17 '17 at 00:34

3 Answers3

5

Present an intermediate loading view with presentModalViewController while the test is taking place and run the actual test using performSelectorInBackground. Then signal back to the main thread with performSelectorOnMainThread:

- (void)viewDidLoad
{
    [super viewDidLoad];

    // Make this a hidden member
    loadingViewController = [LoadingViewController new];
    [self presentModalViewController:loadingViewController animated:NO];

    [self performSelectorInBackground:@selector(testConnectivity) withObject:nil];
}

- (void)testConnectivity
{
    // Do expensive testing stuff

    [self performSelectorOnMainThread:@selector(testCompleted) withObject:nil waitUntilDone:NO];
}

- (void)testCompleted
{
    [loadingViewController dismissViewControllerAnimated:YES];
    loadingViewController = nil;
}

Note that the overall user experience of waiting 30 seconds for the application to start sort of sucks, and connectivity often changing while you are using an app, so even if you do the test every startup it's unlikely to be reliable. But if it is what your boss wants I suffer with you. ;)

Hampus Nilsson
  • 6,692
  • 1
  • 25
  • 29
  • I understand what you are trying to do, and I have exactly a "loading" view showing whenever the app talks to the server(through web services). However blocking the user from doing anything for just detecting whether there is a connection to the server is just not acceptable to us for how long it takes. You are right, even through I can test at the log in time or anytime, the result doesn't guarantee that the every next web service call will go through. I am looking for a way to either detect the connection without effect user experiences or I need a way to detect really really fast. Thanks. – Raymond Wang Jul 11 '12 at 16:41
  • sorry but @Hampus is absolutely right with that. `performSelectorInBackground:` does exactly what it says, doing stuff in the background without blocking the user. Your request takes so long because either your server has issues or you do too many connections at the same time. This is the correct way to handle it and you can show a loading view, or the login view with pre-filled fields while performing the request –  Jul 16 '12 at 11:55
  • and additionally you really want to do the app loading as fast as possible (>30sec -> crash). Therefore you need to load as less data as possible. With this procedure the app has finished loading after 2-3 seconds and you have every time you like to do other stuff (such as performing the request or login) –  Jul 16 '12 at 11:59
  • @relikd: I don't have any custom data loaded into my main view at the beginning. The only thing is auto login if there's an internet connection. If I do have internet, that's not a problem and the code check really fast. It is not the server. If I don't have an internet connection(even though I connected to a local WiFi), it always takes 30 seconds before it can give me anything. And that's my problem. – Raymond Wang Jul 16 '12 at 14:11
  • I count everything as custom data which isn't Apple's stuff. If that 30sec is your only problem you can simply set an timeout for the request to eg. 10sec or even 5sec –  Jul 16 '12 at 14:17
5

For what it's worth, here is the method I use..

- (BOOL)checkServerAvailability {
    bool success = false;
    const char *host_name = [@"host" cStringUsingEncoding:NSASCIIStringEncoding];

    SCNetworkReachabilityRef reachability = 
                  SCNetworkReachabilityCreateWithName(NULL, host_name);
    SCNetworkReachabilityFlags flags;
    success = SCNetworkReachabilityGetFlags(reachability, &flags);
    bool isAvailable = success && (flags & kSCNetworkFlagsReachable) 
                   && !(flags & kSCNetworkFlagsConnectionRequired);

    CFRelease(reachability);
    return isAvailable;
}

Here is the logging result of my server check method...

2012-07-11 11:29:04.892 ASURecycles[1509:f803] starting server check...
2012-07-11 11:29:04.894 ASURecycles[1509:f803] creating reachability...
2012-07-11 11:29:04.894 ASURecycles[1509:f803] creating flags...
2012-07-11 11:29:04.913 ASURecycles[1509:f803] checking for success of reachability, assigning to flags..
2012-07-11 11:29:04.913 ASURecycles[1509:f803] checking our flags to determine network status...
2012-07-11 11:29:04.913 ASURecycles[1509:f803] not available

As you can see, fractions of a second. Of course our server is undergoing trouble at the moment, but I think your issue may be a server issue, not a framework issue. You could always try executing the server check on another thread though.

anon_dev1234
  • 2,143
  • 1
  • 17
  • 33
  • Thanks bgoers, I tried a more complicated version of yours(but same idea). And my experience is, when the device connects to a WiFi, even though the WiFi doesn't have a valid internet connection, the method would still return YES. – Raymond Wang Jul 11 '12 at 17:12
  • It was my understanding that in the event of being able to reach some server, but needing a connection, the SCNetworkReachability would return this: "kSCNetworkReachabilityFlagsConnectionRequired - The specified node name or address can be reached using the current network configuration, but a connection must first be established". That's right from the apple docs. I check for that in my method - if that returns false AND the server is reachable, I return yes, otherwise I return false. That's what the " !(flags & kSCNetworkFlagsConnectionRequired) " is for – anon_dev1234 Jul 11 '12 at 17:26
  • But to answer your question, I have never had an issue with this at all. If there is no connection to a server, it alerts me, and if there is I continue with authentication – anon_dev1234 Jul 11 '12 at 17:28
  • interesting, I'll do a quick test and let you know. – Raymond Wang Jul 11 '12 at 17:28
  • Your method definitely can test the connection when there is WiFi but no internet. I apologize for that. However it is just like apple's Reachability sample, takes about 30 seconds to show me the results. – Raymond Wang Jul 11 '12 at 17:51
  • just updated my answer with an edit. Logged it all - only taking milliseconds to complete the check. – anon_dev1234 Jul 11 '12 at 18:32
  • Thanks but as I said, I wasn't even on internet, how could it be possible to be a server problem? I changed the url to be google.com, remains the same. – Raymond Wang Jul 16 '12 at 14:14
4

As I told in the comments you should use Hampus Nilsson's approach to perform the request in the background anyways.

Regarding to your 30 seconds problem I found this in another blog:

- (BOOL)isHostAvailable:(NSString*)hostName
{
    // this should check the host but does not work in the simulator, aka it returns YES when should be no
    SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithName(NULL, [hostName cStringUsingEncoding:NSASCIIStringEncoding]);
    SCNetworkReachabilityFlags flags;
    BOOL success = SCNetworkReachabilityGetFlags(reachability, &flags);
    if (reachability) {
        CFRelease(reachability);
    }

    if ( ( success && (flags & kSCNetworkFlagsReachable) && !(flags & kSCNetworkFlagsConnectionRequired) ) == NO) {
        return NO;
    }

    // we know at least the network is up, second check for a known page
    NSData *dataReply;
    NSURLResponse *response;
    NSError *error;

    // create the request
    NSString *urlString = [NSString stringWithFormat:@"http://%@/index.php", hostName];
    NSURLRequest *theRequest=[NSURLRequest requestWithURL:[NSURL URLWithString:urlString]
                                            cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
                                        timeoutInterval:8.0];
    // Make the connection
    dataReply = [NSURLConnection sendSynchronousRequest:theRequest returningResponse:&response error:&error];

    if (response != nil) {
        NSLog(@"SNNetworkController.isHostAvailable %@", response);
        return YES;
    } else {
        // inform the user that the download could not be made
        NSLog(@"SNNetworkController.isHostAvailable %@ %@", response, error);
        return NO;
    }
}

this will perform a request with a timeout value of 8 seconds.

//EDIT:
ASIHTTPRequest example:

ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
[request setNumberOfTimesToRetryOnTimeout:3];
[request setTimeOutSeconds:20.0];
[request setRequestMethod:@"POST"];
[request startAsynchronous];
Community
  • 1
  • 1
  • Have you tried this method? This didn't work for me. It is exactly the same 30 seconds. – Raymond Wang Jul 16 '12 at 16:01
  • haven't tried this specific one but used nearly the same in my other app, only difference I used `ASIHTTPRequest` there. You never said that you use other frameworks so I thought would be better to stay at the iOS SDK. But if you start thinking of using a third party SDK then try `AFNetworking` for server communication ;) –  Jul 16 '12 at 18:28
  • Would you post your code for internet checking please? I don't mind to use ASIHTTPRequest if that's just simple internet detection. By the way, the project has been stop developed for over a year and he said "Please note that I am no longer working on this library - you may want to consider using something else for new projects. :)". – Raymond Wang Jul 16 '12 at 18:54
  • therefore I said use `AFNetworking` instead. It's an older project thus I used ASI. I updated the answer with the snippet but you really should use `AFNetworking`. Haven't used that one, but can dig in if you like –  Jul 16 '12 at 19:06
  • OK relikd, I downloaded AFNetworking, what should I do next? Would you show me your code please? Thank you. – Raymond Wang Jul 16 '12 at 19:14
  • hmm actually found that there is no timeout option for POST requests in `AFNetworking` neither in `NSURLRequest`. But as someone mentioned in the comments it is possible to set the timeout (even with `NSURLRequest`) if you set the method to GET. http://stackoverflow.com/q/8304560 –  Jul 16 '12 at 19:43
  • Do you know what *part* of the request is taking so long? For instance, have you ruled out the DNS server you are using? Apple makes it clear in the documentation that it uses DNS, and that can take a long time. – quellish Jul 18 '12 at 19:15