8
\$\begingroup\$

I am novice at creating independent classes for user. I have seen many Reachability library for Swift, but I need to create my own class for that. Here is my code:

class SwiftReachability: NSObject {

let REACHABILITY_NOTIFIER_KEY = "reachability_notifier_key"
var backgroundQueue: NSOperationQueue?
var isCancelled: Bool = false

//MARK: - Checking Internet Connecion
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))
    }

    var flags = SCNetworkReachabilityFlags(rawValue: 0)

    SCNetworkReachabilityGetFlags(defaultRouteReachability!, &flags)

    #if DEBUG
        print("Reachability flags = \(flags.rawValue) uint = \(UInt32(kSCNetworkFlagsReachable))")
    #endif

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

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

func startNotifier(){

    NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(SwiftReachability.networkConnectionObserver), name: REACHABILITY_NOTIFIER_KEY, object: nil)
    callingBackgroungThread()
}

func stopNotifier(){

    NSNotificationCenter.defaultCenter().removeObserver(self, name: REACHABILITY_NOTIFIER_KEY, object: nil)
    backgroundQueue?.cancelAllOperations()
    isCancelled = true
}

func callingBackgroungThread(){

    backgroundQueue = NSOperationQueue()

    let operation = NSBlockOperation {

        if self.isConnectedToNetwork(){

            NSNotificationCenter.defaultCenter().postNotificationName(self.REACHABILITY_NOTIFIER_KEY, object:nil)
        }
    }

    operation.completionBlock = {

        if !self.isCancelled{

            self.callingBackgroungThread()
        }
    }

    delay(5.0, closure: {

        if !self.isCancelled{

            self.backgroundQueue?.addOperation(operation)
        }
    })
}

func networkConnectionObserver(){

    print("Observer")
    showAlert("Network Identified")
}

//MARK: Dispatch Asyn Queue After
func delay(delay:Double, closure:()->()) {

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(delay * Double(NSEC_PER_SEC))), dispatch_get_main_queue(), closure)
}

func showAlert(message: String){

    let alertController = UIAlertController(title: "Bing", message: message, preferredStyle: UIAlertControllerStyle.Alert)

    let okAction = UIAlertAction(title: "Ok", style: UIAlertActionStyle.Default, handler: { (UIAlertAction) in

        self.stopNotifier()
    })

    alertController.addAction(okAction)

    dispatch_async(dispatch_get_main_queue(), {
        let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
        appDelegate.window?.rootViewController!.presentViewController(alertController, animated: true, completion: nil)
    })
}
}

Can anyone review it to make it better?

\$\endgroup\$
0

1 Answer 1

4
\$\begingroup\$

Improvements of your code

Let's start with your isConnectedToNetwork() method.

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

As of Swift 1.2(?), all imported C structs have a default initializer which sets all members to zero:

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

Here defaultRouteReachability is an optional which is later force-unwrapped. Better check with guard and optional binding if the call succeeded:

guard let defaultRouteReachability = withUnsafePointer(&zeroAddress, {
    SCNetworkReachabilityCreateWithAddress(nil, UnsafePointer($0))
}) else {
    NSLog("Could not create reachability reference")
    return false
}
var flags = SCNetworkReachabilityFlags(rawValue: 0)

This can be shorted to

var flags = SCNetworkReachabilityFlags()
// or
var flags : SCNetworkReachabilityFlags = []
let isReachable = (flags.rawValue & UInt32(kSCNetworkFlagsReachable)) != 0
let needsConnection = (flags.rawValue & UInt32(kSCNetworkFlagsConnectionRequired)) != 0

SCNetworkReachabilityFlags is an OptionSetType and that has a set-like interface, there is no need for rawValue and UInt32 conversion:

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

Finally, there is no reason to make this an instance method because it does not use any state, you could make it a type method instead. Then it would look like this:

class func isConnectedToNetwork() -> 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 {
        NSLog("Could not create reachability reference")
        return false
    }

    var flags : SCNetworkReachabilityFlags = []
    SCNetworkReachabilityGetFlags(defaultRouteReachability, &flags)

    let isReachable = flags.contains(.Reachable)
    let needsConnection = flags.contains(.ConnectionRequired)
    return isReachable && !needsConnection
}

Your notification mechanism is far too complicated in my opinion. Instead of scheduling an NSOperation which reschedules itself with dispatch_after, you could simply create a repeating NSTimer. Also you should check if the startNotifier/stopNotifier calls are properly balanced.

Even better, you can register a notification callback which is automatically called when the reachability status changes. I'll come back to that later.


The SwiftReachability class should not do any UI-related stuff like displaying alerts. Use NSNotification or callback methods to make the class reusable.


A different design

Let's have a look at the "Supporting IPv6 DNS64/NAT64 Networks" document from Apple:

Connect Without Preflight

The Reachability APIs (see SCNetworkReachability Reference) are intended for diagnostic purposes after identifying a connectivity issue. Many apps incorrectly use these APIs to proactively check for an Internet connection by calling the SCNetworkReachabilityCreateWithAddress method and passing it an IPv4 address of 0.0.0.0, which indicates that there is a router on the network. However, the presence of a router doesn’t guarantee that an Internet connection exists. In general, avoid preflighting network reachability. Just try to make a connection and gracefully handle failures. If you must check for network availability, avoid calling the SCNetworkReachabilityCreateWithAddress method. Call the SCNetworkReachabilityCreateWithName method and pass it a hostname instead.

Your method can only test for general Internet connectivity (reachability of 0.0.0.0), but not for reachability of a given host (as recommended by Apple).

To add this feature, it makes sense to make the SCNetworkReachability reference a (private) instance variable, and provide multiple init methods:

class SwiftReachability {

    private let reachability: SCNetworkReachability

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

        guard let reachability = withUnsafePointer(&zeroAddress, {
            SCNetworkReachabilityCreateWithAddress(nil, UnsafePointer($0))
        }) else {
            return nil
        }
        self.reachability = reachability
    }

    init?(hostname : String) {
        guard let reachability = SCNetworkReachabilityCreateWithName(nil, hostname) else {
            return nil
        }
        self.reachability = reachability
    }
}

More init methods (e.g. for SCNetworkReachabilityCreateWithAddressPair) can be added if necessary. The initializers are failable.

Checking the connection status consists of two parts:

  • retrieve the SCNetworkReachabilityFlags,
  • evaluate the SCNetworkReachabilityFlags.

For reasons that become more apparent later, I suggest to implement these as separate methods. Retrieving the flags is done as above, only as an instance method:

class SwiftReachability {
    // ...

    func flags() -> SCNetworkReachabilityFlags {
        var flags: SCNetworkReachabilityFlags = []
        SCNetworkReachabilityGetFlags(reachability, &flags)
        return flags
    }
}

Computing the (boolean) connection status from the flags can be done as an extension method of SCNetworkReachabilityFlags:

extension SCNetworkReachabilityFlags {
    func connected() -> Bool {
        let isReachable = self.contains(.Reachable)
        let needsConnection = self.contains(.ConnectionRequired)
        return isReachable && !needsConnection
    }
}

The reachability status can now be determined like this:

if let reachability = SwiftReachability(hostname: "google.com") {
    let connected = reachability.flags().connected()
    print(connected)
} else {
    print("Could not determine reachability status")
}

Change notifications

Instead of polling the connection status in regular intervals, you can register a callback function which is called automatically if the status changes.

class SwiftReachability {
    // ...

    private var notifierActive = false
    var callback : ((SwiftReachability, SCNetworkReachabilityFlags) -> Void)?

    func startNotifier() {
        if notifierActive { return }

        var context = SCNetworkReachabilityContext(version: 0, info: nil,
                                                   retain: nil, release: nil, copyDescription: nil)
        context.info = UnsafeMutablePointer(Unmanaged.passUnretained(self).toOpaque())

        SCNetworkReachabilitySetCallback(reachability, { (_, flags, info) in
            let mySelf = Unmanaged<SwiftReachability>.fromOpaque(COpaquePointer(info)).takeUnretainedValue()
            mySelf.callback?(mySelf, flags)
        }, &context)

        SCNetworkReachabilitySetDispatchQueue(reachability, dispatch_get_main_queue())
        notifierActive = true
    }

    func stopNotifier() {
        if !notifierActive { return }

        SCNetworkReachabilitySetCallback(reachability, nil, nil)
        SCNetworkReachabilitySetDispatchQueue(reachability, nil)
        notifierActive = false
    }

    deinit {
        stopNotifier()
    }
}

This is a bit tricky because the callback is a pure C function. In Swift, this must be a global function or a closure which does not capture any context. It is therefore not possible to use self in the callback closure. Instead, the self instance pointer must be to a Void pointer, passed to the callback, and converted back to an instance pointer (mySelf), compare How to cast self to UnsafeMutablePointer type in swift.

Here is a simple example how this could be used in view controller for regular updates of the connection status:

class ViewController: UIViewController {

    var reachability : SwiftReachability?

    override func viewDidLoad() {
        super.viewDidLoad()

        reachability = SwiftReachability()
        if let reach = reachability {
            reach.callback = { (_, flags) in
                let connected = flags.connected()
                print("Connected:", connected)
                // Update display, show alert, whatever ....
            }
            reach.startNotifier()
        } else {
            print("Could not determine connection status")
            // Update display, show alert, whatever ....
        }
    }
}

Note that the view controller must hold a reference to the reachability object. The connection flags are passed to the callback, so now we can use the connected() extension method which we defined above.

Of course, one could also use the NSNotificationCenter to inform clients of changes in the connection status instead of a callback method.

Remark: There is an excellent implementation of Reachability in Swift: https://github.com/ashleymills/Reachability.swift. Similarities between that code and the code suggested here are not intentional but could not be avoided.

\$\endgroup\$
1
  • \$\begingroup\$ Thanks! I have implemented and made some changes according to the requirement :) \$\endgroup\$ Jul 26, 2016 at 5:31

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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