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.