I am using Reachability class from Apple to detect network events that have impact in the functionality of my app. It is a voip app that uses setKeepAliveTimeout, so every ~10 minutes wakes up reads the network status and decides if the connection should be refreshed.
BOOL res = [app setKeepAliveTimeout:600 handler:^{
[[WIFI instance] isWifiConnected];
[[AClass an_instance] refresh];
}
}];
So every 10 minutes the isWifiConnected is called and the app reads the network status again.
- (BOOL) isWifiConnected {
self.wifiReach = [Reachability reachabilityForLocalWiFi];
NetworkStatus wifiStatus = [self.wifiReach currentReachabilityStatus];
switch (wifiStatus) {
case NotReachable: {
m_wifiConnected = NO;
LOG(#"NetStatus:NotReachable");
break;
}
case ReachableViaWiFi: {
m_wifiConnected = YES;
m_wwanConnected = NO;
LOG(#"NetStatus:ReachableViaWiFi");
break;
}
}
return m_wifiConnected;
}
Although I have WiFi in the device the call returns false, ie no WiFi, and moreover NotReachable for the net status.
However after a very short time interval the reachability callback is called again and the the wifi seems connected. However i have already fired an event due to the error value and the app closes the connection with the server believing that there is no wi-fi.
Doing some research it I found this in the Readme file of the Reachability.m file (provided from Apple)
By default, the application uses www.apple.com for its remote host. You can change the host it uses in APLViewController.m by modifying the value of the remoteHostName variable in -viewDidLoad.
IMPORTANT: Reachability must use DNS to resolve the host name before
it can determine the Reachability of that host, and this may take time
on certain network connections. Because of this, the API will return
NotReachable until name resolution has completed. This delay may be
visible in the interface on some networks
.
Could it this be the problem? The delay in the dns lookup? Or do I need to enhance my code as well?
When I initialize the app I call this
self.hostReach = [Reachability reachabilityWithHostName: #"www.apple.com"];
If I use an IP address like this is correct?
self.hostReach = [Reachability reachabilityWithHostName: #"1.2.3.4"];
Is it safe to use a public IP? eg "17.178.96.59" is the result of an nslookup for apple.com
There is a method in the Reachability class that seems to be used from the Apple's demo.
- (BOOL)connectionRequired
{
NSAssert(_reachabilityRef != NULL, #"connectionRequired called with NULL reachabilityRef");
SCNetworkReachabilityFlags flags;
if (SCNetworkReachabilityGetFlags(_reachabilityRef, &flags))
{
return (flags & kSCNetworkReachabilityFlagsConnectionRequired);
}
return NO;
}
Why the connectionRequired is needed? Can be used to resolved the problem?
To preserve battery charge, iOS will close down the networking hardware when it's not being actively used. This means turning off the WiFi and cellular radios. In this situation, Reachability isn't going to be able to report a complete result, because it can't check the situation without turning everything back on again. That is what kSCNetworkReachabilityFlagsConnectionRequired is for -- to tell you that you need to make a connection to wake the hardware back up.
What you are seeing is probably something waking up when the phone is unlocked (your app or some other app with background permissions) and so everything wakes up, you see an error immediately, but then WiFi connects and you are connected again.
You need to handle Reachability as if it can tell you "definitely reachable" or "definitely not reachable" but also "situation unknown". You need to decide what you're going to do in the unknown situation. If you make a network connection immediately then you will drain the battery faster than normal. An alternative might be just to wait for the network to be woken up for some other reason. That's really up to you.
Reachability should be created with a host name, not an explicit address. The whole point of the DNSsystem is that addresses change for hosts sometimes. Linking directly to a name server should provide some security in this regard, but that isn't how it's supposed to work.
Reachability is often a best guess, not a hard and fast. The only way to be sure is to actually try. Connection required is related to this, because it's the device saying 'everything looks ok, I just haven't tried to connect for real'.
So, arguably you should fire the keep alive without checking the reachability status and use the result of the request to decide if there's an error or not. If you need to be sure, send an actual request and review the actual result.
Related
I'm using NetworkReachability to figure out the connectivity status of my app:
NetworkReachability(this.currentHostUrl);
remoteHostReachability.SetNotification(this.ReachabilityChanged);
remoteHostReachability.Schedule(CFRunLoop.Current, CFRunLoop.ModeDefault);
The callback method looks like this:
void ReachabilityChanged(NetworkReachabilityFlags flags)
{
this.reachable = (flags & NetworkReachabilityFlags.Reachable) > 0;
UIHelpers.GetAppDelegate().UpdateConnectivityToast(this.reachable);
}
Now if I switch to airplane mode, the callback gets called immediately and the flags parameter is 0. Then, shortly after it triggers again and the flags are
ConnectionRequired|IsWWAN|Reachable|TransientConnection
If I turn airplane mode off, I get another 0 and then afterwards
Reachable
If I turn WiFi off and 3G kicks in, the result is:
IsWWAN|Reachable|TransientConnection
It seems like checking for Reachable alone is not enough. But what's the logic here? What do ConnectionRequired and TransientConnection mean?
If there is ConnectionRequired present, then there is actually no connectivity, even though Reachable is present
so it's something like
bool connectionAvailable = (flags.HasFlag(Reachable) && !flags.HasFlags(ConnectionRequired))
Quoting the documentations:
ConnectionRequired: Reachable, but a connection must first be
established.
TransientConnection: The host is reachable using a transient
connection (PPP for example).
Xamarin API Doc and iOS Lib Doc
But you probably can do it like the following sample code:
https://github.com/xamarin/monotouch-samples/blob/master/ReachabilitySample/reachability.cs
It basically checks if Reachable && (!ConnectionRequired || IsWWAN).
What is the best way to get notification while network interface becomes available/unavailable in iOS, different from Reachability? (maybe there is some good way of doing in SystemConfiguration or CFNetworks framework, or somehow using the native unix sockets networking API?)
Instead of checking network reachability I'm just checking if I'm able to get network info with this function, the only problem is when to check. I don't want to fire up an NSTimer every 0.1 second to do this, though it is solution, I would rather like to somehow get notified when user switches WiFi on/off in settings. (Reachability takes several seconds to nofity me when Im disabling wwan interface in settings.)
- (void)initializeCurrentNetworkInfo
{
NSArray* interfacesSupported = (__bridge_transfer NSArray*) CNCopySupportedInterfaces();
NSLog(#"%s: Supported interfaces: %#", __func__, interfacesSupported);
NSDictionary* interfaceInfo = nil;
for (NSString *interfaceName in interfacesSupported) {
interfaceInfo = (__bridge_transfer NSDictionary*) CNCopyCurrentNetworkInfo((__bridge_retained CFStringRef)interfaceName);
if (interfaceInfo && [interfaceInfo count]) {
self.isInterfaceActive = YES;
self.currentSSID = [NSString stringWithString:interfaceInfo[#"SSID"]];
self.currentBSSID = [NSString stringWithString:interfaceInfo[#"BSSID"]];
break;
} else if (!interfaceInfo){
self.isInterfaceActive = NO;
self.currentBSSID = #"ff:ff:ff:ff:ff:ff";;
self.currentSSID = #"interface is unavailable";
}
}
}
Info: I'm developing the UPNP application, and each time interface becomes available/unavailable I subsequently initialise or nullify my UPNP service object.
The problem is that in some rare occasions my app crashes, while I'm changing Wi-Fi switch on/off consequently for the purpose of testing. (Reachability works fine in the background)
So I could have traced it and found it that it obviously crashes while trying to receive an httpu datagram on socket of no longer available interface, when switching wifi off (recvLen = cg_socket_recv(sock, dgmPkt);), if I understand it right, it means that the background thread listening on socket in my UPNP framework (CyberGarages' CyberLink) is unaware of interface state changes(bug probably?), that's why I really want to stop the upnpService as fast as possible once the interface state changes.
I'm using the Tony Millions' Reachability version, and once I receive networkReachabilityChanged: I check for available interfaces, but I guess it's not the best way of doing it.
Thanks.
I am having issues with Reachability not detecting that Wifi is active connection. I have the standard Reachability class imported in my project, and on the action I perform I detect if Wifi is available to determine which action to actually perform.
Reachability *reachability = [Reachability reachabilityWithHostName:#"www.google.com"];
NetworkStatus status = [reachability currentReachabilityStatus];
NSLog(#"%i", status);
if (status == ReachableViaWiFi) {
//do wifi action
}
else {
//do non wifi action
}
No matter what, it always performs the else action. The log for the status returns '2'. Any thoughts what may be going on?
UPDATE: After much testing, I have determined that for some reason, WWAN and WIFI are reversed on this app. When running on a real device with wifi turned off, it shows connection status as WIFI and when connected to a WIFI network it shows connection status as WWAN.
we took a look at the programming and narrowed it down to a switch case issue, reachability was returning 2 but the switch case inside another class returned "wwan" instead of "wifi" for case 2, please check this answer or close this question so others will not try to answer a solved problem, Thank You.
Does anyone know if there is a (really) fast way to check for an inactive internet connection?
I have implemented the solution suggested here and it work but takes about 30 sec to determine if there was no internet connection.
I have also tried out:
- (BOOL) isConnectionAvailable {
SCNetworkReachabilityFlags flags;
BOOL receivedFlags;
SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithName(CFAllocatorGetDefault(), [#"www.google.com" UTF8String]);
receivedFlags = SCNetworkReachabilityGetFlags(reachability, &flags);
CFRelease(reachability);
return (!receivedFlags || flags == 0) ? FALSE : TRUE;
}
which works too but it still takes about 20-30 seconds to determine if there is no active internet connection.
I feel that it got to be a fast way to determine if there is no internet connection.
Would really appreciate if someone can point me in a good direction.
I guess the "fastest" way, would be to simply utilize the connection with minimal data. Just set an appropriate timeout that works for you.
Apple even recommended at WWDC, that you should just simply try to establish a connection, because the reachability API might provide an inaccurate answer with certain conditions. The only way to measure properly is by sending a real request.
You can make a background thread in you application that will be checking if there is internet connection.
I use this customised reachability class in one of my apps: (code put elsewhere due to length)
Reachability.h - http://pastebin.com/qUVp4tFb
Reachability.m - http://pastebin.com/3C8LUjkS
Make sure to #import it, and call the following which returns a BOOL:
([[Reachability reachabilityForInternetConnection] currentReachabilityStatus] == ReachableViaWWAN);
It takes around a second to determine if the internet connection is active or not with this class.
Hope that helped :)
I am trying to make my iPhone app more robust, by ensuring that it doesn't crash when there is no network connection. Right now the app attempts to make a connection on startup immediately, through the app delegate. It has no issues if wifi or cellular is available, but it will crash if there is no network connection visible. I have looked around on this site and haven't found anything that quite fits my problem. I have the feeling it should be just a simple line of code, like an objective-c equivalent of a pseudo- 'isConnection', or something similar:
if (isConnection) {
- sendSynchronousRequest for json data I'm using
- manipulate the data, etc., and continue with normal operations
} else {
- send an output message to a view controller,
letting the user know what's wrong.
}
I can't seem to isolate the (admittedly abstract) "isConnection" condition that I'm looking for, specifically. Does anyone have experience or advice with this topic?
The reachability class is very easy to use. Download the class files here https://developer.apple.com/library/ios/#samplecode/Reachability/Introduction/Intro.html
You also need to add the SystemConfiguration.framework
Here's the code you need:
-(BOOL)isConnection {
Reachability *reach = [Reachability reachabilityWithHostName:#"www.google.com"];
//replace www.google.com with your own host you're checking for
NetworkStatus hostStatus = [reach currentReachabilityStatus];
if (hostStatus != NotReachable) {
//There are also other status enums like
//ReachableViaWiFi
//ReachableViaWWAN (3G/LTE)
//if you need to detect if user is on cellular, act accordingly
return YES;
}
return NO;
}
Then you can call your method:
if ([self isConnection]) {
//do something
} else {
//no connection, inform user
}
You can use the Reachability class that Apple provides in the Reachability sample application. Not only does it tell you if you're connected, but how you're connected (WiFi, cellular or no connection at all). You can even register for a notification when the connection status changes.
Even though you can use that to check the connection status before initiating the code you describe above, I still think you should investigate why the app crashes when you have no connection, and attempt to resolve it. You could always lose connectivity in the middle of a request attempt, and you don't want that to crash the app either.