17

I'm developing an iOS application written in Swift that communicates with a HTTP server on the local network, and I'm using Apple's Reachability class to determine wether the remote machine running the HTTP server is online or not. Here's the code:

...
let RemoteHost: String = "192.168.178.130"
var RemoteReachability: Reachability! = nil
var RemoteIsReachable: Bool = false

init() {
        super.init()
        self.RemoteReachability = Reachability(hostName: self.RemoteHost)
        NSNotificationCenter.defaultCenter().addObserver(self, selector: "reachabilityChanged:", name: kReachabilityChangedNotification, object: self.RemoteReachability)
        self.RemoteReachability.startNotifier()
        self.RemoteIsReachable = (self.RemoteReachability.currentReachabilityStatus().value == ReachableViaWiFi.value)
}

func reachabilityChanged(notification: NSNotification) {
    let ReachabilityInst: Reachability = notification.object as Reachability
    self.RemoteIsReachable = (ReachabilityInst.currentReachabilityStatus().value == ReachableViaWiFi.value)
}

The problem is that no matter if the remote machine is online or offline,

(ReachabilityInst.currentReachabilityStatus().value == ReachableViaWiFi.value)

Is always true, as long as I'm connected to a Wifi network. However, when I turn Wifi off, it results in being false instead of true. Am I doing something wrong here, or is the Reachability class just not compatible with Swift/xCode 6 Beta yet? I've also tried this:

(ReachabilityInst.currentReachabilityStatus() == ReachableViaWiFi)

But that results in xCode telling me "Could not find an overload for '==' that accepts the supplied arguments", even though both appear to be of type 'NetworkStatus'.

Thanks in advance.

rmaddy
  • 314,917
  • 42
  • 532
  • 579
pseudorandomuser
  • 315
  • 1
  • 2
  • 10

4 Answers4

38

The Reachability class you're using is based on Apple's SCNetworkReachability class, which doesn't do exactly what you're hoping. From the SCNetworkReachability documentation:

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.

So it's not built for testing whether or not the remote host is actually online, just whether (1) the current network settings will allow an attempt to reach it and (2) by what methods. Once you've determined that the network is active you'll need to make an attempt to connect to see if the remote host is actually up and running.


Note: This test:

(ReachabilityInst.currentReachabilityStatus().value == ReachableViaWiFi.value)

is the correct way to check -- for some reason NetworkStatus is one of the few Apple enumerations created without the NS_ENUM macro.

Nate Cook
  • 92,417
  • 32
  • 217
  • 178
12

In swift,

About what I understood of reachability utility propose by Apple (https://developer.apple.com/library/ios/samplecode/Reachability/Introduction/Intro.html) or tonymillion (https://github.com/tonymillion/Reachability) which are basically the same is :

you have 3 possible test :

  • local (can access local network but not internet)
  • extern (can access internet by ip address)
  • dns (can access internet and reach an hostname)

You can test them respectively by starting a notifier with this :

let wifiReachability = Reachability. reachabilityForLocalWiFi()
wifiReachability.startNotifier()

let internetReachability = Reachability.reachabilityForInternetConnection()
hostReachability.startNotifier()

let hostReachability = Reachability(hostName:"www.apple.com")
hostReachability.startNotifier()

Which will trigger a notification that you can catch with this method : NSNotificationCenter.defaultCenter().addObserver()

So to use them you can do something like that :

create a function in your appDelegate which will instantiate the notifier :

func startReachabilityTest()
{
    // Allocate a reachability object to test internet access by hostname
    let reach = Reachability(hostName: "www.apple.com")

    // Tell the reachability that we DON'T want to be reachable on 3G/EDGE/CDMA
    //reach.reachableOnWWAN = false

    reach.startNotifier()
}

Then you can call it in the didFinishLaunchingWithOptions of your appDelegate :

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
    self.startReachabilityTest();
}

if want to catch the event in any viewController just add this line to viewDidLoad :

override func viewDidLoad()
{
    // Here we set up a NSNotification observer. The Reachability that caused the notification
    // is passed in the object parameter
    NSNotificationCenter.defaultCenter().addObserver(
            self,
            selector: "reachabilityChanged:",
            name: kReachabilityChangedNotification,
            object: nil)
}

and add this method method to react to the event :

func reachabilityChanged(notice: NSNotification)
{
    println("reachability changed")
    let reach = notice.object as? Reachability
    if let remoteHostStatus = reach?.currentReachabilityStatus()
    {
        if remoteHostStatus == NetworkStatus.NotReachable
        {
            println("not reachable")
        }
        else
        {
            println("reachable")
        }
    }
}

You can also catch the event inside your appDelegate by adding NSNotificationCenter.defaultCenter().addObserver() inside didFinishLaunchingWithOptions and then add reachabilityChanged(notice: NSNotification) in it.

Ah, also note that you can easily add reachability class to your project with cocoapods by adding this line :

pod 'Reachability', '~> 3.2'

To your pod file and them after a pod install in command line just add this line to xxx-Bridging-Header.h header file (where xxx is the name of your app) :

#import <Reachability/Reachability.h>

If you don't have bridging header in your project you can follow this tutorial : http://www.learnswiftonline.com/getting-started/adding-swift-bridging-header/

No need to add systemConfiguration.framework which is already added by pod dependencies.

Note : Reachability does not work fine in the simulator

hope this help!

Dragouf
  • 4,676
  • 4
  • 48
  • 55
  • There seems to be a copy/paste error in your post. See [this suggested edit](http://stackoverflow.com/review/suggested-edits/12075165) that was (unfortunately) rejected. – Nisse Engström Apr 20 '16 at 02:39
2

If you're looking for a Swift implementation of Apple's Reachability class, you could take a look at this:

http://github.com/ashleymills/Reachability.swift

It's a drop in class, using notifications and closures.

It works with iOS and OS X and has Cocoapod / Carthage support.

Good luck!

Ashley Mills
  • 50,474
  • 16
  • 129
  • 160
  • Tested this lib today on iOS 9, it does not work at all, although the idea is very good. I'm sticking with Apple's Objective-C written class for now. But I'm looking forward to improvements. – Valentin Mercier Jul 09 '15 at 16:40
  • In what sense didn't it work? Did you try the Swift 2 beta version? Please raise an issue on github with details of your problem and I'll take a look. – Ashley Mills Jul 09 '15 at 16:42
  • I should have explained better. I have indeed used the Swift 2 version. It compiled fine with no error, but whenever I change my connection (Wifi/Cellular) or go to airplane mode, the notification callback is not called. And sometimes it randomly crashes. The Xcode debugger does not show anything, it just says it has lost the connection to the iPhone, and the app just crashes. However I have experimented the same issue with Apple's Objective-C lib so I'm guessing the problem is common to both codes. Probably iOS 9 related, I don't know :/ – Valentin Mercier Jul 09 '15 at 17:11
  • EDIT: it works fine on the simulator, but crashes on my iPhone. – Valentin Mercier Jul 09 '15 at 17:21
  • FINAL EDIT: It works well, even on my iPhone but crashes if I run the app on my iPhone via xCode. But if I run it manually by pressing the app icon from the SpringBoard then it works nice. – Valentin Mercier Jul 09 '15 at 17:41
  • 1
    And none of the problems mentioned above occur if the Cellular Hotspot is disabled. Okay. – Valentin Mercier Jul 09 '15 at 18:03
0

Using combine and Reachability:

import Combine
import Reachability
import os

class ReachabilityStore: ObservableObject {
    private var reachability: Reachability

    @Published var reachable: Bool = false
    @Published var reachableViaWifi: Bool = false
    @Published var reachableViaCellular: Bool = false

    init() {
        reachability = try! Reachability()

        reachability.whenReachable = { [weak self] reachability in
            guard let self = self else { return }

            self.reachable = true
            self.reachableViaWifi = reachability.connection == .wifi
            self.reachableViaCellular = !self.reachableViaWifi

            os_log(
                "Reachable via %{public}s",
                self.reachableViaWifi ? "WiFi" : "Cellular"
            )
        }

        reachability.whenUnreachable = { [weak self] _ in
            guard let self = self else { return }

            os_log("Unreachable")

            self.reachable = false
            self.reachableViaWifi = false
            self.reachableViaCellular = false
        }

        do {
            try reachability.startNotifier()
        } catch {
            os_log("Unable to start reachability notifier.")
        }
    }
}
Mycroft Canner
  • 1,828
  • 1
  • 11
  • 24