How to interpret NetworkReachabilityFlags in Xamarin.iOS? - ios

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).

Related

Alamofire - detect internet connection

How to detect connection to internet?
I have a viewController that displays data fetched from a server. It is presented modally.
I display a message asking for an internet connection if a user is not connected to the internet. Otherwise, I show the data.
When a user connects to the internet without leaving the app, how to detect it? I assume it is possible to do it in Alamofire.
I have used the following code in viewDidLoad and viewWillAppear, but the listener is not called - If I connect & disconnect to WIFI, startListening is not called.
let network = NetworkReachabilityManager()
network!.startListening { status in
if status == .reachable(.cellular) || status == .reachable(.ethernetOrWiFi) {
self.fetchProducts()
} else {
self.noInternetConnnection()
}
}
FYI, you can unable both WIFI and Mobile Data in control center.
If you're declaring network inside a function it's probably being deallocated as soon as the scope ends.
Make sure you store it in an instance variable.

Why does NWPathMonitor status is always satisfied?

When there is no connection I get an error from the URL Session saying that the request timed out.
I’m using the Network protocol to check for connectivity before hand but apparently this is not working as when I am calling this inside viewDidLoad:
static func startUpdateProcess() {
let monitor = NWPathMonitor()
monitor.pathUpdateHandler = { path in
if path.status == .satisfied {
print("Good! We are connected!")
Helper.createDownloadTask()
} else {
print("No connection. Local file not updated!")
}
}
let queue = DispatchQueue(label: "Monitor")
monitor.start(queue: queue)
}
...I get “Good! We are connected!”.
Shouldn’t the path not be satisfied if there is no connection and therefore trigger the else statement?
FYI the createDownloadTask() questions the API and downloads the required data.
Can you tell me what is wrong here and what could I do to get to the else statement if the path is not satisfied?
Thank you!
Credit to user May Rest in Peace for pointing me to the right direction.
Despite the Documentation being silent on the Network Protocol, it seems that the status property of the NWPath class, an enumeration of type NWPath.Status, returns .satisfied as long as the device is connected to a network, regardless of whether that network is working, transmitting data, or not.
The only way the else statement above could be triggered would have been by deactivating Wi-Fi and/or Cellular data or disconnecting from any network before launching the app.
All those properties are listed in the Documentation but none of them has a description or a discussion attached.
This article by user #twostraws allowed me to create the first part of that code.
Reference to the instance of NWPathMonitor (aka monitor in your scenario) needs to be retained.
You could make it a strong property by making monitor a class level property so that its lifecycle is the same as the place you are referring it from. It looks like the monitor object is being released effectively stopping the callbacks for network status monitoring.

iOS Reachability reports no WiFi, when WiFi is active

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.

How to check if internet connection is inactive - as fast as possible?

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 :)

ios diagnosing iphone connection status

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.

Resources