HiveBrain v1.2.0
Get Started
← Back to all entries
patternswiftMinor

Checking network status

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
checkingnetworkstatus

Problem

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) {
SCNetworkReachabilityC

Solution

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) -> (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")
}

Code Snippets

public static func fromCStringRepairingIllFormedUTF8(cs: UnsafePointer<CChar>) -> (String?, hadError: Bool)
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"
        }
    }
}
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
        }
    }
}
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)
}
switch status {
case .Unknown, .Offline:
    print("not connected")
case .Online(.WWAN):
    print("connected via WWan")
case .Online(.WiFi):
    print("connected via WiFi")
}

Context

StackExchange Code Review Q#107129, answer score: 3

Revisions (0)

No revisions yet.