How to use SCNetworkReachability in Swift
Asked Answered
E

8

103

I'm trying to convert this code snippet to Swift. I'm struggling on getting off the ground due to some difficulties.

- (BOOL) connectedToNetwork
{
    // Create zero addy
    struct sockaddr_in zeroAddress;
    bzero(&zeroAddress, sizeof(zeroAddress));
    zeroAddress.sin_len = sizeof(zeroAddress);
    zeroAddress.sin_family = AF_INET;

    // Recover reachability flags
    SCNetworkReachabilityRef defaultRouteReachability = SCNetworkReachabilityCreateWithAddress(NULL, (struct sockaddr *)&zeroAddress);
    SCNetworkReachabilityFlags flags;

    BOOL didRetrieveFlags = SCNetworkReachabilityGetFlags(defaultRouteReachability, &flags);
    CFRelease(defaultRouteReachability);

    if (!didRetrieveFlags)
    {
        return NO;
    }

    BOOL isReachable = flags & kSCNetworkFlagsReachable;
    BOOL needsConnection = flags & kSCNetworkFlagsConnectionRequired;

    return (isReachable && !needsConnection) ? YES : NO;
}

The first and the main issue I'm having is on how to define and work with C structs. In the first line (struct sockaddr_in zeroAddress;) of the above code, I think they're defining a instance called zeroAddress from the struct sockaddr_in(?), I assume. I tried declaring a var like this.

var zeroAddress = sockaddr_in()

But I get the error Missing argument for parameter 'sin_len' in call which is understandable because that struct takes a number of arguments. So I tried again.

var zeroAddress = sockaddr_in(sin_len: sizeof(zeroAddress), sin_family: AF_INET, sin_port: nil, sin_addr: nil, sin_zero: nil)

As expected I get some other error Variable used within its own initial value. I understand the cause of that error too. In C, they declare the instance first and then fill up the parameters. Its not possible in Swift as far as I know. So I'm truly lost at this point on what to do.

I read Apple's official document on interacting with C APIs in Swift but it has no examples in working with structs.

Can anyone please help me out here? I'd really appreciate it.

Thank you.


UPDATE: Thanks to Martin I was able to get past the initial problem. But still Swift ain't making it easier for me. I'm getting multiple new errors.

func connectedToNetwork() -> Bool {

    var zeroAddress = sockaddr_in(sin_len: 0, sin_family: 0, sin_port: 0, sin_addr: in_addr(s_addr: 0), sin_zero: (0, 0, 0, 0, 0, 0, 0, 0))
    zeroAddress.sin_len = UInt8(sizeofValue(zeroAddress))
    zeroAddress.sin_family = sa_family_t(AF_INET)

    var defaultRouteReachability: SCNetworkReachabilityRef = SCNetworkReachabilityCreateWithAddress(UnsafePointer<Void>, UnsafePointer<zeroAddress>) // 'zeroAddress' is not a type
    var flags = SCNetworkReachabilityFlags()

    let didRetrieveFlags = SCNetworkReachabilityGetFlags(defaultRouteReachability, UnsafeMutablePointer<flags>) // 'flags' is not a type
    defaultRouteReachability.dealloc(1) // 'SCNetworkReachabilityRef' does not have a member named 'dealloc'

    if didRetrieveFlags == false {
        return false
    }

    let isReachable: Bool = flags & kSCNetworkFlagsReachable // Cannot invoke '&' with an argument list of type '(@lvalue UInt32, Int)'
    let needsConnection: Bool = flags & kSCNetworkFlagsConnectionRequired // Cannot invoke '&' with an argument list of type '(@lvalue UInt32, Int)'

    return (isReachable && !needsConnection) ? true : false
}

EDIT 1: Okay I changed this line to this,

var defaultRouteReachability: SCNetworkReachabilityRef = SCNetworkReachabilityCreateWithAddress(UnsafePointer<Void>(), &zeroAddress)

The new error I'm getting at this line is 'UnsafePointer' is not convertible to 'CFAllocator'. How to you pass NULL in Swift?

Also I changed this line and the error is gone now.

let didRetrieveFlags = SCNetworkReachabilityGetFlags(defaultRouteReachability, &flags)

EDIT 2: I passed nil in this line after seeing this question. But that answer contradicts with the answer here. It says there is no equivalent to NULL in Swift.

var defaultRouteReachability: SCNetworkReachabilityRef = SCNetworkReachabilityCreateWithAddress(nil, &zeroAddress)

Anyway I get a new error saying 'sockaddr_in' is not identical to 'sockaddr' at the above line.

Emeldaemelen answered 2/9, 2014 at 12:17 Comment(2)
i am having error at line if !SCNetworkReachabilityGetFlags(defaultRouteReachability, &flags) i.e unary operator ! cannot be applied to an operand of type Boolean. . . . please help.Prohibitionist
I urge you to use the below answer: https://mcmap.net/q/48801/-how-to-use-scnetworkreachability-in-swiftCaiaphas
U
243

(This answer was extended repeatedly due to changes in the Swift language, which made it a bit confusing. I have now rewritten it and removed everything which refers to Swift 1.x. The older code can be found in the edit history if somebody needs it.)

This is how you would do it in Swift 2.0 (Xcode 7):

import SystemConfiguration

func connectedToNetwork() -> Bool {

    var zeroAddress = sockaddr_in()
    zeroAddress.sin_len = UInt8(sizeofValue(zeroAddress))
    zeroAddress.sin_family = sa_family_t(AF_INET)

    guard let defaultRouteReachability = withUnsafePointer(&zeroAddress, {
        SCNetworkReachabilityCreateWithAddress(nil, UnsafePointer($0))
    }) else {
        return false
    }

    var flags : SCNetworkReachabilityFlags = []
    if !SCNetworkReachabilityGetFlags(defaultRouteReachability, &flags) {
        return false
    }

    let isReachable = flags.contains(.Reachable)
    let needsConnection = flags.contains(.ConnectionRequired)

    return (isReachable && !needsConnection)
}

Explanations:

  • As of Swift 1.2 (Xcode 6.3), imported C structs have a default initializer in Swift, which initializes all of the struct's fields to zero, so the socket address structure can be initialized with

    var zeroAddress = sockaddr_in()
    
  • sizeofValue() gives the size of this structure, this has to be converted to UInt8 for sin_len:

    zeroAddress.sin_len = UInt8(sizeofValue(zeroAddress))
    
  • AF_INET is an Int32, this has to be converted to the correct type for sin_family:

    zeroAddress.sin_family = sa_family_t(AF_INET)
    
  • withUnsafePointer(&zeroAddress) { ... } passes the address of the structure to the closure where it is used as argument for SCNetworkReachabilityCreateWithAddress(). The UnsafePointer($0) conversion is needed because that function expects a pointer to sockaddr, not sockaddr_in.

  • The value returned from withUnsafePointer() is the return value from SCNetworkReachabilityCreateWithAddress() and that has the type SCNetworkReachability?, i.e. it is an optional. The guard let statement (a new feature in Swift 2.0) assigns the unwrapped value to the defaultRouteReachability variable if it is not nil. Otherwise the else block is executed and the function returns.

  • As of Swift 2, SCNetworkReachabilityCreateWithAddress() returns a managed object. You don't have to release it explicitly.
  • As of Swift 2, SCNetworkReachabilityFlags conforms to OptionSetType which has a set-like interface. You create an empty flags variable with

    var flags : SCNetworkReachabilityFlags = []
    

    and check for flags with

    let isReachable = flags.contains(.Reachable)
    let needsConnection = flags.contains(.ConnectionRequired)
    
  • The second parameter of SCNetworkReachabilityGetFlags has the type UnsafeMutablePointer<SCNetworkReachabilityFlags>, which means that you have to pass the address of the flags variable.

Note also that registering a notifier callback is possible as of Swift 2, compare Working with C APIs from Swift and Swift 2 - UnsafeMutablePointer<Void> to object.


Update for Swift 3/4:

Unsafe pointers cannot be simply be converted to a pointer of a different type anymore (see - SE-0107 UnsafeRawPointer API). Here the updated code:

import SystemConfiguration

func connectedToNetwork() -> Bool {

    var zeroAddress = sockaddr_in()
    zeroAddress.sin_len = UInt8(MemoryLayout<sockaddr_in>.size)
    zeroAddress.sin_family = sa_family_t(AF_INET)

    guard let defaultRouteReachability = withUnsafePointer(to: &zeroAddress, {
        $0.withMemoryRebound(to: sockaddr.self, capacity: 1) {
            SCNetworkReachabilityCreateWithAddress(nil, $0)
        }
    }) else {
        return false
    }

    var flags: SCNetworkReachabilityFlags = []
    if !SCNetworkReachabilityGetFlags(defaultRouteReachability, &flags) {
        return false
    }

    let isReachable = flags.contains(.reachable)
    let needsConnection = flags.contains(.connectionRequired)

    return (isReachable && !needsConnection)
}
Udine answered 2/9, 2014 at 12:38 Comment(16)
Hi Martin, thanks a lot for helping me out. I'm getting a couple of more errors. Can you please have a look? I updated my question. Sorry about this. I just don't know C at all so this is pretty hard for me.Emeldaemelen
I'll keep updating the question with my attempts and results.Emeldaemelen
@Isuru: See update. There were various problems. Let me know if there are special points that need more explanation.Udine
Thanks a ton! I just tried it and it works perfectly. I posted the code up on Github so someone might find it useful. Can you please give a brief overview of what the code does please? Like what are SCNetworkReachabilityFlags and what does SCNetworkReachabilityCreateWithAddress do etc? Thank you very much again :)Emeldaemelen
@Isuru: Sorry, but that is a bit too broad. I was prepared to explain why the code is translated in this way. But for general questions about the Reachability functions I would suggest that you refer to the documentation first.Udine
Alright, I will do that. Can you just explain what this UnsafePointer($0) is? That's the most confusing part for me. What is $0?Emeldaemelen
@Isuru: UnsafePointer is the Swift equivalent of a C pointer. withUnsafePointer(&zeroAddress) calls the following closure { ...} with the address of zeroAddress as argument. Inside the closure, $0 stands for that argument. - Sorry, it is impossible to explain all that in a few sentences. Have a look at the documentation about closures in the Swift book. $0 is a "shorthand argument name".Udine
Just an update: SCNetworkReachabilityGetFlags(defaultRouteReachability, &flags) == 0 breaks with the newest Swift 2 update (Xcode 7 Beta 5). I needed to change the 0 to false.Stepaniestepbrother
@JAL: You are right, Apple changed how a "Boolean" is mapped to Swift. Thanks for your feedback, I will update the answer accordingly.Udine
This returns true if wifi is not connected and 4G is on but the user has specified that the app may not use cellular data.. Any solutions?Uralaltaic
@Jugale: Check the documentation of SCNetworkReachabilityGetFlags (). There are various flags that you can check for, e.g. kSCNetworkReachabilityFlagsIsWWAN.Udine
@Jugale: You could do something like: let cellular = flags.contains(.IsWWAN) You may return a touple instead of a Boolean, like: func connectedToNetwork() -> (connected: Bool, cellular: Bool)Lytle
how to check a reachability for our own custom address? I mean instead of passing nil in SCNetworkReachabilityCreateWithAddress(nil, $0) , can we pass some address instead of nil ? (maybe google.com, or mycustomUrl.com)Septa
@Tejas: You can use any IP address instead of the "zero address", or use SCNetworkReachabilityCreateWithName() with a host name as string. But note that SCNetworkReachability only checks that a packet sent to that address can leave the local device. It does not guarantee that the data packet will actually be received by the host.Udine
Would a firewall interfere with this working properly? If so, what settings would allow it to function as it should?Harriott
@user3225395: See developer.apple.com/documentation/systemconfiguration/… or https://mcmap.net/q/49179/-using-apple-39-s-reachability-to-check-remote-server-reachability-in-swift/1187415: "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." – Roughly speaking, SCNetworkReachability only determines if the next "hop" (Router, Firewall, ...) can be reached. For everything else, you must try to actually connect to the host.Udine
I
15

Swift 5, Using NWPathMonitor

import Network

func configureNetworkMonitor(){
        let monitor = NWPathMonitor()
        
        monitor.pathUpdateHandler = { path in
            
            if path.status != .satisfied {
                print("not connected")
            }
            else if path.usesInterfaceType(.cellular) {
                print("Cellular")
            }
            else if path.usesInterfaceType(.wifi) {
                print("WIFI")
            }
            else if path.usesInterfaceType(.wiredEthernet) {
                print("Ethernet")
            }
            else if path.usesInterfaceType(.other){
                print("Other")
            }else if path.usesInterfaceType(.loopback){
                print("Loop Back")
            }
        }
        
        monitor.start(queue: DispatchQueue.global(qos: .background))
    }
Infirmary answered 10/9, 2020 at 20:52 Comment(3)
This needs more upvotes. This is much cleaner and more user friendly than the SystemConfiguration framework.Horace
Agreed. Works in xCode 13.3 and Swift 5Encyclopedist
not always works as expected and gives wrong resultWits
C
13

Swift 3, IPv4, IPv6

Based on the Martin R's answer:

import SystemConfiguration

func isConnectedToNetwork() -> Bool {
    guard let flags = getFlags() else { return false }
    let isReachable = flags.contains(.reachable)
    let needsConnection = flags.contains(.connectionRequired)
    return (isReachable && !needsConnection)
}

func getFlags() -> SCNetworkReachabilityFlags? {
    guard let reachability = ipv4Reachability() ?? ipv6Reachability() else {
        return nil
    }
    var flags = SCNetworkReachabilityFlags()
    if !SCNetworkReachabilityGetFlags(reachability, &flags) {
        return nil
    }
    return flags
}

func ipv6Reachability() -> SCNetworkReachability? {
    var zeroAddress = sockaddr_in6()
    zeroAddress.sin6_len = UInt8(MemoryLayout<sockaddr_in>.size)
    zeroAddress.sin6_family = sa_family_t(AF_INET6)

    return withUnsafePointer(to: &zeroAddress, {
        $0.withMemoryRebound(to: sockaddr.self, capacity: 1) {
            SCNetworkReachabilityCreateWithAddress(nil, $0)
        }
    })
}

func ipv4Reachability() -> SCNetworkReachability? {
    var zeroAddress = sockaddr_in()
    zeroAddress.sin_len = UInt8(MemoryLayout<sockaddr_in>.size)
    zeroAddress.sin_family = sa_family_t(AF_INET)

    return withUnsafePointer(to: &zeroAddress, {
        $0.withMemoryRebound(to: sockaddr.self, capacity: 1) {
            SCNetworkReachabilityCreateWithAddress(nil, $0)
        }
    })
}
Comedienne answered 16/12, 2016 at 1:35 Comment(2)
Working for me also best way for NET64/IPV6 also, don't forget to import SystemConfigurationStructural
@juanjo, how do you set a host you want to reach using your codeAbdominous
F
6

This has nothing to do with Swift, but the best solution is to NOT use Reachability to determine whether the network is online. Just make your connection and handle errors if it fails. Making a connection can at times fire up the dormant offline radios.

The one valid use of Reachability is to use it to notify you when a network transitions from offline to online. At that point you should retry failed connections.

Featherstone answered 15/10, 2015 at 21:45 Comment(4)
SCNetworkReachability != Reachability.Stepaniestepbrother
Still buggy. Just make the connection and handle errors. See openradar.me/21581686 and mail-archive.com/[email protected]/msg00200.html and the first comment here mikeash.com/pyblog/friday-qa-2013-06-14-reachability.htmlFeatherstone
I don't get this - wouldn't you want to know whether you were on WiFi or 3G before attempting a big upload?Psycho
Historically, Reachability didn't work if the radios were powered down. I haven't tested this on modern devices in iOS 9, but I guarantee that it used to cause upload failures in earlier versions of iOS when simply making a connection would have worked fine. If you want an upload to only go via WiFi, you should use the NSURLSession API with NSURLSessionConfiguration.allowsCellularAccess = false.Featherstone
N
3

The best solution is to use ReachabilitySwift class, written in Swift 2, and uses SCNetworkReachabilityRef.

Simple and easy:

let reachability = Reachability.reachabilityForInternetConnection()

reachability?.whenReachable = { reachability in
    // keep in mind this is called on a background thread
    // and if you are updating the UI it needs to happen
    // on the main thread, like this:
    dispatch_async(dispatch_get_main_queue()) {
        if reachability.isReachableViaWiFi() {
            print("Reachable via WiFi")
        } else {
            print("Reachable via Cellular")
        }
    }
}
reachability?.whenUnreachable = { reachability in
    // keep in mind this is called on a background thread
    // and if you are updating the UI it needs to happen
    // on the main thread, like this:
    dispatch_async(dispatch_get_main_queue()) {
        print("Not reachable")
    }
}

reachability?.startNotifier()

Working like a charm.

Enjoy

Nefarious answered 15/10, 2015 at 11:41 Comment(1)
I prefer the accepted answer as it does not require integrating any third party dependencies. Additionally, this does not answer the question of how to use SCNetworkReachability class in Swift, it is a suggestion of a dependency to use to check for a valid network connection.Stepaniestepbrother
H
3

A SwiftUI take on Mithra Sigam's solution above:

import SwiftUI
import Network

class NetworkReachabilityManager: ObservableObject {
    @Published var networkPathStatus: NWPath.Status
    @Published var availableInterfaces: [NWInterface]
    
    let monitor = NWPathMonitor()
    
    init() {
        monitor.start(queue: DispatchQueue.global(qos: .background))
        
        let currentPath = monitor.currentPath
        
        networkPathStatus = currentPath.status
        availableInterfaces = currentPath.availableInterfaces
        
        monitor.pathUpdateHandler = { [self] networkPath in
            DispatchQueue.main.async {
                networkPathStatus = networkPath.status
                availableInterfaces = networkPath.availableInterfaces
            }
        }
    }
    
    deinit {
        monitor.cancel()
    }
}
Horace answered 28/1, 2021 at 16:10 Comment(0)
A
1

updated juanjo's answer to create singleton instance

import Foundation
import SystemConfiguration

final class Reachability {

    private init () {}
    class var shared: Reachability {
        struct Static {
            static let instance: Reachability = Reachability()
        }
        return Static.instance
    }

    func isConnectedToNetwork() -> Bool {
        guard let flags = getFlags() else { return false }
        let isReachable = flags.contains(.reachable)
        let needsConnection = flags.contains(.connectionRequired)
        return (isReachable && !needsConnection)
    }

    private func getFlags() -> SCNetworkReachabilityFlags? {
        guard let reachability = ipv4Reachability() ?? ipv6Reachability() else {
            return nil
        }
        var flags = SCNetworkReachabilityFlags()
        if !SCNetworkReachabilityGetFlags(reachability, &flags) {
            return nil
        }
        return flags
    }

    private func ipv6Reachability() -> SCNetworkReachability? {
        var zeroAddress = sockaddr_in6()
        zeroAddress.sin6_len = UInt8(MemoryLayout<sockaddr_in>.size)
        zeroAddress.sin6_family = sa_family_t(AF_INET6)

        return withUnsafePointer(to: &zeroAddress, {
            $0.withMemoryRebound(to: sockaddr.self, capacity: 1) {
                SCNetworkReachabilityCreateWithAddress(nil, $0)
            }
        })
    }
    private func ipv4Reachability() -> SCNetworkReachability? {
        var zeroAddress = sockaddr_in()
        zeroAddress.sin_len = UInt8(MemoryLayout<sockaddr_in>.size)
        zeroAddress.sin_family = sa_family_t(AF_INET)

        return withUnsafePointer(to: &zeroAddress, {
            $0.withMemoryRebound(to: sockaddr.self, capacity: 1) {
                SCNetworkReachabilityCreateWithAddress(nil, $0)
            }
        })
    }
}

Usage

if Reachability.shared.isConnectedToNetwork(){

}
Accursed answered 3/5, 2017 at 16:1 Comment(0)
M
1

This is in Swift 4.0

I am using this framework https://github.com/ashleymills/Reachability.swift
And Install Pod ..
In AppDelegate

var window: UIWindow?
var reachability = InternetReachability()!
var reachabilityViewController : UIViewController? = nil

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    // Override point for customization after application launch.

    reachabilityChecking()
    return true
}

extension AppDelegate {

func reachabilityChecking() {    
    reachability.whenReachable = { reachability in
        DispatchQueue.main.async {
            print("Internet is OK!")
            if reachability.connection != .none && self.reachabilityViewController != nil {

            }
        }
    }
    reachability.whenUnreachable = { _ in
        DispatchQueue.main.async {
            print("Internet connection FAILED!")
            let storyboard = UIStoryboard(name: "Reachability", bundle: Bundle.main)
            self.reachabilityViewController = storyboard.instantiateViewController(withIdentifier: "ReachabilityViewController")
            let rootVC = self.window?.rootViewController
            rootVC?.present(self.reachabilityViewController!, animated: true, completion: nil)
        }
    }
    do {
        try reachability.startNotifier()
    } catch {
        print("Could not start notifier")
    }
}
}

The reachabilityViewController screen will appear if internet is not there

Malita answered 19/2, 2019 at 13:31 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.