I'm using the Reachability class to monitor network connection and server availability in a couple of iPhone apps. I have the following code in my AppDelegate:
// Observe the kNetworkReachabilityChangedNotification. When that notification is posted, the
// method "reachabilityChanged" will be called.
[[NSNotificationCenter defaultCenter] addObserver: self selector: #selector(reachabilityChanged:) name: kReachabilityChangedNotification object: nil];
// Start checking for reachability to myWebService.com
//Change the host name here to change the server your monitoring
hostReach = [Reachability reachabilityWithHostName: #"myWebService.com"];
[hostReach startNotifier];
internetReach = [Reachability reachabilityForInternetConnection];
[internetReach startNotifier];
wifiReach = [Reachability reachabilityForLocalWiFi];
[wifiReach startNotifier];
When I profile one of the apps (a dedicated blog reader for my companies blog) using Instruments, I see a difference of +5.9 Mb of network traffic over a one minute period when I turn on the notifiers, vs commenting those lines out.
What is the proper way to check for network connection before sending a request to a server? I probably shouldn't be monitoring the connection full time, but only when I need to know.
My other app communicates with a web service API to post pages and video, and I want to know if I have a connection to that service before attempting to post, but I want to minimize network traffic where I can.
I'd guess that this is one thing Apple looks for when approving/rejecting apps in the app store.
Instead of checking the connection before you make the request, just check for a network connection error when you do make the request. You can periodically make requests if you want to update the status of the network.
Related
I am working on an iOS app that is configured to work with Pulse Secure VPN. I have subscribed to the reachability change notification to log network down scenarios. The below code in AppDelegate.m is working fine as it is. If per app VPN is enabled, it does not recognize network change(LTE to Airplane Mode and vice versa).
-(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.reachability = [Reachability reachabilityForInternetConnection];
[self.reachability startNotifier];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(reachabilityChanged:) name:kReachabilityChangedNotification object:nil];
}
- (void) reachabilityChanged:(NSNotification *)note
{
Reachability* curReach = [note object];
NetworkStatus netStatus = [reachability currentReachabilityStatus];
switch (netStatus)
{
case NotReachable:
{
break;
}
case ReachableViaWWAN:
{
break;
}
case ReachableViaWiFi:
{
break;
}
}
}
It would be helpful to know if I need to do extra configuration for VPN enabled app. I cannot use reachabilityWithHostName: since the app connects to various domains.
Interesting. I happen to be the author of a VPN app which uses the Reachability class, so I was able to set some breakpoints and see what's going on.
My app supports both OpenVPN and IKEv2. I tried both, and here's what I found:
IKEv2 (via Apple's NetworkExtension framework): Reachability messages are delivered correctly, even while a VPN connection is active. (Verified by pulling ethernet cable).
OpenVPN: Reachability messages are not delivered while the VPN is connected. Pulled plug, re-plugged, no notifications came in until I disconnected the VPN from within the app.
It wasn't immediately clear from their website which backend Pulse VPN uses. If they're using OpenVPN you might be outta luck.
The way OpenVPN works is by injecting itself into your network traffic via the "tun" and "tap" devices (think tunnel and wiretap). In doing this, it might suppress whatever Apple uses for reachability notification generation. You'd probably need to talk to Apple for that level of specificity, though.
The heavy-handed solution would be to do an HTTP GET to a server somewhere, on a timer. That's as inelegant as it gets, so use with caution and only in the most dire of circumstances.
I'm writing an app that has separated functions when online and offline. In those function, I use Reachability to check internet connection and with each case (online/offline), it does different works.
Now, I've been required to write test cases for those business logics. I've searched everywhere, but it seems that no one care much about test case in iOS.
I'd like to write testcase that cover for both online/offline case. Does it possible in iOS? If so, how do I simulate the internet connection status?
UPDATE QUESTION:
I also want to cover the case switching from online to offline and vice versa. There should be a way to simulate this network connection status, right?
You should mock return value using OCMock
id reachabilityMock = OCMClassMock([Reachability class]);
[OCMStub([reachabilityMock currentReachabilityStatus]) andReturnValue:#(ReachableViaWiFi)];
[OCMStub([reachabilityMock reachabilityForInternetConnection]) andReturn:reachabilityMock];
Reachability *reachability = [Reachability reachabilityForInternetConnection];
XCTAssertEqual(reachability.currentReachabilityStatus, ReachableViaWiFi);
Then send reachability changed notification manually.
[[NSNotificationCenter defaultCenter] postNotificationName: kReachabilityChangedNotification object: noteObject];
Across my app I am using NSURLConnection to make any server related API Calls.
I am using the function NSURLConnection sendAsynchronousRequest queue completionHandler to make async requests to the server. Sometimes these requests fail because there is no internet connection.
How would I use Apple's Reachability Class in conjunction with NSURLConnection?
You just need to do that:
Reachability *reachability = [Reachability reachabilityForInternetConnection];
NetworkStatus internetStatus = [reachability currentReachabilityStatus];
if (internetStatus != NotReachable) {
// connection ok: call NSURLConnection sendAsynchronousRequest queue completionHandler here
}
else {
// No connection
}
Be careful to check if the network is provided by wifi or not before download big data. You can check this through the internetStatus.
The correct way to use reachability is as follows:
Make the request.
If it fails, create a reachability listener for the specific host that you're trying to reach (reachabilityWithHostName:).
When that class indicates that reachability has changed, check to see if it says that it is now reachable, and if so, dispose of the reachability object and re-issue the request.
What you absolutely should not do is gate the initial request on reachability. The reachability APIs can get out of sync with reality, causing your app to fail to make requests that would otherwise have succeeded.
It is, however, acceptable to limit the retry rate of periodic requests based on overall reachability. However, if any of those requests succeeds, your app should then act as though reachability gave it a green light even if it didn't.
Is it Possible for an app to know whether the connection has been switched from Wi-Fi to 3G While app is in background?The requirement is that download should happen on Wi-Fi Connection.The Check is done initialy but i need to confirm that download is happening on Wi-Fi and should be cancelled if the user has switched to 3G/4G while app is in Background.
If you just want to make sure downloads only happen on WiFi, then you should set the allowsCellularAccess property of the NSURLSession's NSURLSessionConfiguration to NO.
No monitoring, cancelling or other tricks needed: that'll make sure the download never goes over the cellular connection.
I suggest you this library
Rechability
It uses NSNotification, and you can make some changes in it if you need to set it your way
This is a example code provided by this library
// Allocate a reachability object
Reachability* reach = [Reachability reachabilityWithHostname:#"www.google.com"];
// Tell the reachability that we DON'T want to be reachable on 3G/EDGE/CDMA
reach.reachableOnWWAN = NO;
// Here we set up a NSNotification observer. The Reachability that caused the notification
// is passed in the object parameter
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(reachabilityChanged:)
name:kReachabilityChangedNotification
object:nil];
[reach startNotifier];
Hope it helps
I'm using Reachability successfully to determine the status of the network, and to be notified of changes (e.g. Reachability Guide for iOS 4).
My question isn't how to get Reachability up and running, but rather the following.
My AppDelegate handles the Reachability stuff. The app receives notifications (kReachabilityChangedNotification) while the app is running, and when the app is in the Background (applicationDidEnterBackground:).
The app is designed to reload a playing audio stream when it notices that it's lost a Wi-Fi connection, e.g. To test, I turned Wi-Fi on and off in Settings, and everything worked perfectly. In real-world testing, I often lose Wi-Fi connectivity when I exit the range of the access point. I've found that Reachability isn't helping me too much in this case. I'm not sure if it's because Reachability notifications don't come through when the screen is locked, or if Reachability doesn't handle the slow diminishing of signal from an increasingly distant Wi-Fi access point, but regardless I can't figure out why the real-world testing doesn't match the idealized case.
This is what my code looks like. I first set up to receive notifications, and start listening to Reachability:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// check for internet connection
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(checkNetworkStatus:)
name:kReachabilityChangedNotification object:nil];
// Set up Reachability
internetReachable = [[Reachability reachabilityForInternetConnection] retain];
[internetReachable startNotifier];
....
return YES;
}
and then, this is the function that responds to changes in connectivity:
- (void)checkNetworkStatus:(NSNotification *)notice {
// called after network status changes
NetworkStatus internetStatus = [internetReachable currentReachabilityStatus];
switch (internetStatus)
{
case NotReachable:
{
NSLog(#"The internet is down.");
break;
}
case ReachableViaWiFi:
{
NSLog(#"The internet is working via WIFI");
break;
}
case ReachableViaWWAN:
{
NSLog(#"The internet is working via WWAN!");
break;
}
}
}
The notifications come through even when the app is in the background, but they don't in the real-world testing described above.
Thanks for any help.
By default in the background state app stays for a short time only, most apps move to the suspended state shortly afterward. That mean the app is in the background but is not executing code. So your custom implemented notification do not work.
Must requery NetworkReachability at Wakeup Time in app delegate methodes:
applicationWillEnterForeground:
applicationDidBecomeActive
I was working on a VoIP app, which is launched in the background when the iPhone boots up, at which point there might be no network reachability (e.g. if the phone has both a passcode and/or a SIM card with a PIN code). But since the app is launched directly in the background, the delegate method applicationDidEnterBackground: is not called. Instead what I did was use #Hurden's idea directly in application:didFinishLaunchingWithOptions, checking the applicationState to see if the app was actually starting in the background. This enabled my app to get the kReachabilityChangedNotification notification after the phone was unlocked (enabling the WiFi to connect using the stored password).