5
\$\begingroup\$

I have this slightly popular Swift library called IJReachability up on Github. It checks the network connection status. Due to my office workload I've been very busy in the past few months so I couldn't attend to the project and keep in touch with the folks who are contributing.

Now I'm back and hoping to dust off the project, update to Swift 2 (probably still keep the 1.2 version in a separate branch) and activly participate on developing it.

The project was originally written in Swift 1.2:

import Foundation
import SystemConfiguration

public enum IJReachabilityType {
    case WWAN,
    WiFi,
    NotConnected
}

public class IJReachability {

    public class func isConnectedToNetwork() -> 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)

        let defaultRouteReachability = withUnsafePointer(&zeroAddress) {
            SCNetworkReachabilityCreateWithAddress(nil, UnsafePointer($0)).takeRetainedValue()
        }

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

        let isReachable = (flags & UInt32(kSCNetworkFlagsReachable)) != 0
        let needsConnection = (flags & UInt32(kSCNetworkFlagsConnectionRequired)) != 0

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

    public class func isConnectedToNetworkOfType() -> IJReachabilityType {

        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)

        let defaultRouteReachability = withUnsafePointer(&zeroAddress) {
            SCNetworkReachabilityCreateWithAddress(nil, UnsafePointer($0)).takeRetainedValue()
        }

        var flags: SCNetworkReachabilityFlags = 0
        if SCNetworkReachabilityGetFlags(defaultRouteReachability, &flags) == 0 {
            return .NotConnected
        }

        let isReachable = (flags & UInt32(kSCNetworkFlagsReachable)) != 0
        let isWWAN = (flags & UInt32(kSCNetworkReachabilityFlagsIsWWAN)) != 0

        if(isReachable && isWWAN){
            return .WWAN
        }
        if(isReachable && !isWWAN){
            return .WiFi
        }

        return .NotConnected
    }

}

One method checks if you're currently connected to network or not and the other one returns the type of the connection. And as you can see there is quite a bit of code duplication.

I've updated the project to Swift 2 with the help of the pull requests I got:

import Foundation
import SystemConfiguration


let ReachabilityStatusChangedNotification = "ReachabilityStatusChangedNotification"

enum ReachabilityType: CustomStringConvertible {
    case WWAN
    case WiFi
    case NotConnected

    var description: String {
        switch self {
        case .WWAN: return "WWAN"
        case .WiFi: return "WiFi"
        case .NotConnected: return "NotConnected"
        }
    }
}

enum ReachabilityStatus: CustomStringConvertible  {
    case Offline
    case Online
    case Unknown

    var description: String {
        switch self {
        case .Offline: return "Offline"
        case .Online: return "Online"
        case .Unknown: return "Unknown"
        }
    }
}

public class Reach {

    func connectedToNetwork() -> (status: ReachabilityStatus, type: ReachabilityType) {
        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 (status: .Offline, type: .NotConnected)
        }

        var flags : SCNetworkReachabilityFlags = []
        if !SCNetworkReachabilityGetFlags(defaultRouteReachability, &flags) {
            return (status: .Offline, type: .NotConnected)
        }

        let isReachable = flags.contains(.Reachable)
        let isWWAN = flags.contains(.IsWWAN)

        if isReachable && isWWAN {
            return (status: .Online, type: .WWAN)
        }

        if isReachable && !isWWAN {
            return (status: .Online, type: .WiFi)
        }

        return (status: .Offline, type: .NotConnected)
    }


    func monitorReachabilityChanges() {
        let host = "google.com"
        var context = SCNetworkReachabilityContext(version: 0, info: nil, retain: nil, release: nil, copyDescription: nil)
        let reachability = SCNetworkReachabilityCreateWithName(nil, host)!

        SCNetworkReachabilitySetCallback(reachability, { (_, flags, _) in
            var status: ReachabilityStatus!
            var type: ReachabilityType!

            let connectionRequired = flags.contains(.ConnectionRequired)
            let isReachable = flags.contains(.Reachable)
            let isWWAN = flags.contains(.IsWWAN)

            if !connectionRequired && flags.contains(.Reachable) {
                if isReachable && isWWAN {
                    status = .Online
                    type = .WWAN
                }

                if isReachable && !isWWAN {
                    status = .Online
                    type = .WiFi
                }
            } else {
                status =  .Offline
                type = .NotConnected
            }

            NSNotificationCenter.defaultCenter().postNotificationName(ReachabilityStatusChangedNotification,
                object: nil,
                userInfo: ["Status": status.description, "Type": type.description])

            }, &context)

        SCNetworkReachabilityScheduleWithRunLoop(reachability, CFRunLoopGetMain(), kCFRunLoopCommonModes)
    }

}

But I did something different. Instead of having two functions for the network status and the type, I consolidated the functionality of both functions into one. I also added a new function to monitor the network connectivity in the background (this wasn't possible in Swift 1.2 due to language limitations).

I have two questions:

  1. The connectedToNetwork() function returns a tuple packing both the status and type. Is this a good way to do this? Even though this is possible, I've never seen tuples used much in Apple and third party frameworks/libraries.
  2. There is still some code duplication. Specifically checking the flags variable for what it contains. I'm not sure how to refactor this in an optimal way.
\$\endgroup\$
3
\$\begingroup\$

Using a tuple as return type is generally fine. I haven't seen it much either in Apple's frameworks, the only one that I know of at present is the String method

public static func fromCStringRepairingIllFormedUTF8(cs: UnsafePointer<CChar>) -> (String?, hadError: Bool)

In your particular case however, I think there is a better way. The reachability type is only relevant if the status is .Online, and not all type/status combinations make sense.

I would therefore include the type into the status by using enums with associated values:

enum ReachabilityType: CustomStringConvertible {
    case WWAN
    case WiFi
    // No case NotConnected anymore.

    var description: String {
        switch self {
        case .WWAN: return "WWAN"
        case .WiFi: return "WiFi"
        }
    }
}

enum ReachabilityStatus: CustomStringConvertible  {
    case Offline
    case Online(ReachabilityType) // Type as an associated value.
    case Unknown

    var description: String {
        switch self {
        case .Offline: return "Offline"
        case .Online(let type): return "Online(\(type))"
        case .Unknown: return "Unknown"
        }
    }
}

Now your connectedToNetwork() function should return a ReachabilityStatus, and possible values are: .Unknown, .Offline, .Online(.WWAN) and .Online(.WiFi). A better name for that function might be connectionStatus().

The .Unknown status is currently not used at all. You might return that if the status could not be determined (e.g. retrieving the flags failed).

The code duplication for evaluating the flags can be solved by defining an (perhaps private) init method of ReachabilityStatus which takes an SCNetworkReachabilityFlags parameter:

extension ReachabilityStatus {
    private init(reachabilityFlags flags: SCNetworkReachabilityFlags) {
        let connectionRequired = flags.contains(.ConnectionRequired)
        let isReachable = flags.contains(.Reachable)
        let isWWAN = flags.contains(.IsWWAN)

        if !connectionRequired && isReachable {
            if isWWAN {
                self = .Online(.WWAN)
            } else {
                self = .Online(.WiFi)
            }
        } else {
            self =  .Offline
        }
    }
}

so that your function would now look like this:

func connectionStatus() -> ReachabilityStatus {
    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 .Unknown
    }

    var flags : SCNetworkReachabilityFlags = []
    if !SCNetworkReachabilityGetFlags(defaultRouteReachability, &flags) {
        return .Unknown
    }
    return ReachabilityStatus(reachabilityFlags: flags)
}

More remarks: Instead of using the NSNotificationCenter you could pass a closure as a callback to the monitorReachabilityChanges() function. That would save you from the hassle to wrap (and later unwrap) the status into some Objective-C compatible type. (Added: However, that would require more changes because a C callback cannot capture context. See Swift 2 - UnsafeMutablePointer to object for a possible solution.)

Note also that you need some method to stop the monitoring, i.e. to unregister the reachability from the run loop.

All information get be retrieved from this single ReachabilityStatus value with the associated ReachabilityType. You can check for all possible values with a switch statement:

switch status {
case .Unknown, .Offline:
    print("not connected")
case .Online(.WWAN):
    print("connected via WWan")
case .Online(.WiFi):
    print("connected via WiFi")
}

or use if/case with pattern matching to check for a certain state:

if case .Online = status  {
    print("online")
} else {
    print("offline")
}

if case .Online(.WiFi) = status {
    print("connected via WiFi")
}
\$\endgroup\$
  • \$\begingroup\$ Thanks Martin for the response. That does indeed resolve all the duplication issues. But what if you need to know not only the status but the type of the connection too? I know it's returned from the description property but that's as a string. You cannot compare it with the enum values, right? \$\endgroup\$ – Isuru Oct 10 '15 at 17:17
  • \$\begingroup\$ @Isuru: See updated answer ... \$\endgroup\$ – Martin R Oct 10 '15 at 21:35
  • \$\begingroup\$ Thanks for including more info. I tried updating the monitorReachabilityChanges() function adding a closure. Here's the code. But when I callback from inside the SCNetworkReachabilitySetCallback closure, I get the error A C function pointer cannot be formed from a closure that captures context. \$\endgroup\$ – Isuru Oct 11 '15 at 16:13
  • \$\begingroup\$ @Isuru: I see. The problem is that a C callback cannot capture context (which I overlooked when writing my first remark). The solution is to use a pure C callback and passing context to it via opaque pointers. My suggestion here stackoverflow.com/questions/30786883/… made it into another slightly popular Swift library: github.com/ashleymills/Reachability.swift/blob/master/… \$\endgroup\$ – Martin R Oct 11 '15 at 16:22
  • \$\begingroup\$ Okay I modified the class a little. I also added a method to stop the monitoring as you suggested. Code. I get the error attempt to create an Unmanaged instance from a null pointer when creating the obj variable inside the callback function though. \$\endgroup\$ – Isuru Oct 11 '15 at 17:58

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.