0

In my app I want to make a network call only if I can access the internet.

Note: I'm connected to a WiFi spot that doesn't have Internet

I want to test if Internet is available. I tried using Reachability as described here and I also tried a simpler solution as described here

The problem is that with Reachability is that it returns that the Internet is reachable when it's not. The other solution takes too much time to give a response. I tried to set timeout intervals for the request and the session, but it's getting ignored.

How can I test for internet reachability in case I'm connected to a wifi with no Internet?

Here some code that I use:

- (void) postAsyncTaskWithUrl:(NSString*)urlString
                      andType:(NSString*)requestType
                     andToken:(NSString*)token
          andPropertiesObject:(id)propObject
                   urlEncoded:(BOOL)isUrlEncoded
                  withSuccess:(nullable void(^)(id _Nullable))success
                   andFailure:(nullable void(^)(id _Nullable))failure
{

    internetReachableFoo = [Reachability reachabilityWithHostname:@"www.google.com"];

    __weak typeof(self) weakSelf = self;
    // Internet is reachable
    internetReachableFoo.reachableBlock = ^(Reachability*reach)
    {
                 NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:urlSt];
        [request setTimeoutInterval:20]; //Ignored ... WHY?

        NSURLSessionConfiguration *sessionConfigurations = [NSURLSessionConfiguration defaultSessionConfiguration];
        [sessionConfigurations setTimeoutIntervalForRequest:20]; //Ignored ... WHY?
        [sessionConfigurations setTimeoutIntervalForResource:20]; //Ignored ... WHY?
//        NSURLSession *session = [NSURLSession sharedSession];
        NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfigurations];
        [[session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {


     }

}

}
Keselme
  • 2,052
  • 2
  • 21
  • 41
  • You can check this: [How to know whether actual internet is available or not in Swift?](https://stackoverflow.com/q/52550568/3687801) – nayem Jan 08 '20 at 16:46

3 Answers3

5

Despite this being something that was historically done in the past, I don't believe that it's worth trying to check if the network is reachable, as it isn't something that's reliable or something that should be done before making a request in almost all cases now.

According to Apple's official documentation:

Always attempt to make a connection. Do not attempt to guess whether network service is available, and do not cache that determination

Similar blog post from Jared Sinclair on this topic, using SCNetworkReachability (which is what the Reachability library uses under the hood):

SCNetworkReachability should only be used to influence what you do about a network request that has already failed, not an initial request that has yet to be attempted. Use a negative status to determine whether or not you attempt an automatic retry, or to tweak the user-facing language of an alert. Use a positive status to consider retrying an earlier failed request. Never prevent a user-initiated request from being attempted just because SCNetworkReachability thinks there’s not a reachable network

Borrowing from a recent Reddit thread:

Reachability is one or "these topics" in iOS world. What does it mean that the Internet is reachable? Even if you check network interfaces, you may not be able to reach apple.com or google.com. Even if you reach Google, you may not be able to reach your on-prem server or cloud. If you check whether you can reach your server of interest, why not to send a request straight away?

Seb Jachec
  • 2,800
  • 2
  • 30
  • 55
  • Thanks for the reply, I'm actually fine with trying to send the request without knowing if there's internet or not so I tried to set a timeout of 10 seconds which should be enough I think. However the app completely ignores this and I'm stuck for more than a minute waiting for the reply. What can cause the timeout to be ignored? – Keselme Jan 08 '20 at 16:21
  • 1
    I'm not sure. It could be due to the fact that the timeout only terminates the request if there's absolutely no data transferred within 10 seconds, it doesn't terminate the request if it starts within 10 seconds then goes on to take 60 seconds (https://stackoverflow.com/a/35428764/447697) – Seb Jachec Jan 08 '20 at 16:30
2

TL;DR: You probably want an NSTimer.

In my app I want to make a network call only if I can access the internet.

This is not a meaningful statement. There is no test you can perform that reliably means you can "access the internet" (despite the existence of NSURLErrorNotConnectedToInternet, which doesn't actually mean what it says). For example, your code is trying to treat "www.google.com" as "the internet." But it is completely possible to have a network that permits packets to go to www.google.com, but nowhere else. Or to permit ICMP packets, but not HTTP packets. Or to permit HTTP packets, but not on certain ports. Such network configurations are not just possible, they're common, and in various ways they all can look like "not on the internet."

The only thing you can answer is "can I send a packet to a specific host and receive a packet in return?" It's not even possible to know whether you can just send a packet. If you don't get something in return, you don't know if your packet was delivered or not.

Reachability answers the question "if I tried to send a packet, would my local network stack even try?" It is basically the question of whether there is a first-hop available in the routing table. It doesn't mean you can actually connect; it can only tell you that it absolutely won't.

The timeouts you're using are based on idle-time on a session. But you never start the session, so it never idles. The timeout you're looking for is the TCP timeout, which I expect to be 4 minutes. NSURLSession can't configure it.

If you want a wall-clock "start-to-finish" timeout, then you need to run it yourself with a Timer.

Rob Napier
  • 250,948
  • 34
  • 393
  • 528
  • you say I never start the session, but isn't calling for `[[session dataTaskWithRequest:request completionHandler:handler] resume]` means that I start it? – Keselme Jan 09 '20 at 07:27
  • No, I mean that the TCP session never connects, so at the URLSession layer, the idle timer doesn't start. – Rob Napier Jan 09 '20 at 13:46
  • I tried to implement the `NSTimer` mechanism, but it doesn't work either, as soon as I do `dataTaskWithRequest` when connected to a WiFi without Internet, the whole app freezes for about two minutes. Only when it comes back, then the timer's selector fires. I don't understand what might cause that freeze because from my understanding `dataTaskWithRequest` runs on its own thread. – Keselme Jan 09 '20 at 16:10
0

If you try to load http://captive.apple.com it will return hardcoded string “Success” as a body if internet works or will redirect / fail. You can check for response starts to be 200 and body to be “success”. That’s what Apple is using to automatically show the credentials window when connected to airport like WiFi’s Saying that a) there’s no foolproof solution b) you will be wasting user’s data (might be ok in your use case?)

Sash Zats
  • 5,006
  • 2
  • 25
  • 42