aboutsummaryrefslogtreecommitdiff
path: root/DWA140Menu/Reachability.swift
diff options
context:
space:
mode:
Diffstat (limited to 'DWA140Menu/Reachability.swift')
-rwxr-xr-xDWA140Menu/Reachability.swift316
1 files changed, 316 insertions, 0 deletions
diff --git a/DWA140Menu/Reachability.swift b/DWA140Menu/Reachability.swift
new file mode 100755
index 0000000..8711a71
--- /dev/null
+++ b/DWA140Menu/Reachability.swift
@@ -0,0 +1,316 @@
+/*
+Copyright (c) 2014, Ashley Mills
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation
+and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+*/
+
+import SystemConfiguration
+import Foundation
+
+public enum ReachabilityError: Error {
+ case FailedToCreateWithAddress(sockaddr_in)
+ case FailedToCreateWithHostname(String)
+ case UnableToSetCallback
+ case UnableToSetDispatchQueue
+ case UnableToGetInitialFlags
+}
+
+@available(*, unavailable, renamed: "Notification.Name.reachabilityChanged")
+public let ReachabilityChangedNotification = NSNotification.Name("ReachabilityChangedNotification")
+
+public extension Notification.Name {
+ public static let reachabilityChanged = Notification.Name("reachabilityChanged")
+}
+
+public class Reachability {
+
+ public typealias NetworkReachable = (Reachability) -> ()
+ public typealias NetworkUnreachable = (Reachability) -> ()
+
+ @available(*, unavailable, renamed: "Connection")
+ public enum NetworkStatus: CustomStringConvertible {
+ case notReachable, reachableViaWiFi, reachableViaWWAN
+ public var description: String {
+ switch self {
+ case .reachableViaWWAN: return "Cellular"
+ case .reachableViaWiFi: return "WiFi"
+ case .notReachable: return "No Connection"
+ }
+ }
+ }
+
+ public enum Connection: CustomStringConvertible {
+ case none, wifi, cellular
+ public var description: String {
+ switch self {
+ case .cellular: return "Cellular"
+ case .wifi: return "WiFi"
+ case .none: return "No Connection"
+ }
+ }
+ }
+
+ public var whenReachable: NetworkReachable?
+ public var whenUnreachable: NetworkUnreachable?
+
+ @available(*, deprecated: 4.0, renamed: "allowsCellularConnection")
+ public let reachableOnWWAN: Bool = true
+
+ /// Set to `false` to force Reachability.connection to .none when on cellular connection (default value `true`)
+ public var allowsCellularConnection: Bool
+
+ // The notification center on which "reachability changed" events are being posted
+ public var notificationCenter: NotificationCenter = NotificationCenter.default
+
+ @available(*, deprecated: 4.0, renamed: "connection.description")
+ public var currentReachabilityString: String {
+ return "\(connection)"
+ }
+
+ @available(*, unavailable, renamed: "connection")
+ public var currentReachabilityStatus: Connection {
+ return connection
+ }
+
+ public var connection: Connection {
+ if flags == nil {
+ try? setReachabilityFlags()
+ }
+
+ switch flags?.connection {
+ case .none?, nil: return .none
+ case .cellular?: return allowsCellularConnection ? .cellular : .none
+ case .wifi?: return .wifi
+ }
+ }
+
+ fileprivate var isRunningOnDevice: Bool = {
+ #if targetEnvironment(simulator)
+ return false
+ #else
+ return true
+ #endif
+ }()
+
+ fileprivate var notifierRunning = false
+ fileprivate let reachabilityRef: SCNetworkReachability
+ fileprivate let reachabilitySerialQueue: DispatchQueue
+ fileprivate(set) var flags: SCNetworkReachabilityFlags? {
+ didSet {
+ guard flags != oldValue else { return }
+ reachabilityChanged()
+ }
+ }
+
+ required public init(reachabilityRef: SCNetworkReachability, queueQoS: DispatchQoS = .default, targetQueue: DispatchQueue? = nil) {
+ self.allowsCellularConnection = true
+ self.reachabilityRef = reachabilityRef
+ self.reachabilitySerialQueue = DispatchQueue(label: "uk.co.ashleymills.reachability", qos: queueQoS, target: targetQueue)
+ }
+
+ public convenience init?(hostname: String, queueQoS: DispatchQoS = .default, targetQueue: DispatchQueue? = nil) {
+ guard let ref = SCNetworkReachabilityCreateWithName(nil, hostname) else { return nil }
+ self.init(reachabilityRef: ref, queueQoS: queueQoS, targetQueue: targetQueue)
+ }
+
+ public convenience init?(queueQoS: DispatchQoS = .default, targetQueue: DispatchQueue? = nil) {
+ var zeroAddress = sockaddr()
+ zeroAddress.sa_len = UInt8(MemoryLayout<sockaddr>.size)
+ zeroAddress.sa_family = sa_family_t(AF_INET)
+
+ guard let ref = SCNetworkReachabilityCreateWithAddress(nil, &zeroAddress) else { return nil }
+
+ self.init(reachabilityRef: ref, queueQoS: queueQoS, targetQueue: targetQueue)
+ }
+
+ deinit {
+ stopNotifier()
+ }
+}
+
+public extension Reachability {
+
+ // MARK: - *** Notifier methods ***
+ func startNotifier() throws {
+ guard !notifierRunning else { return }
+
+ let callback: SCNetworkReachabilityCallBack = { (reachability, flags, info) in
+ guard let info = info else { return }
+
+ let reachability = Unmanaged<Reachability>.fromOpaque(info).takeUnretainedValue()
+ reachability.flags = flags
+ }
+
+ var context = SCNetworkReachabilityContext(version: 0, info: nil, retain: nil, release: nil, copyDescription: nil)
+ context.info = UnsafeMutableRawPointer(Unmanaged<Reachability>.passUnretained(self).toOpaque())
+ if !SCNetworkReachabilitySetCallback(reachabilityRef, callback, &context) {
+ stopNotifier()
+ throw ReachabilityError.UnableToSetCallback
+ }
+
+ if !SCNetworkReachabilitySetDispatchQueue(reachabilityRef, reachabilitySerialQueue) {
+ stopNotifier()
+ throw ReachabilityError.UnableToSetDispatchQueue
+ }
+
+ // Perform an initial check
+ try setReachabilityFlags()
+
+ notifierRunning = true
+ }
+
+ func stopNotifier() {
+ defer { notifierRunning = false }
+
+ SCNetworkReachabilitySetCallback(reachabilityRef, nil, nil)
+ SCNetworkReachabilitySetDispatchQueue(reachabilityRef, nil)
+ }
+
+ // MARK: - *** Connection test methods ***
+ @available(*, deprecated: 4.0, message: "Please use `connection != .none`")
+ var isReachable: Bool {
+ return connection != .none
+ }
+
+ @available(*, deprecated: 4.0, message: "Please use `connection == .cellular`")
+ var isReachableViaWWAN: Bool {
+ // Check we're not on the simulator, we're REACHABLE and check we're on WWAN
+ return connection == .cellular
+ }
+
+ @available(*, deprecated: 4.0, message: "Please use `connection == .wifi`")
+ var isReachableViaWiFi: Bool {
+ return connection == .wifi
+ }
+
+ var description: String {
+ guard let flags = flags else { return "unavailable flags" }
+ let W = isRunningOnDevice ? (flags.isOnWWANFlagSet ? "W" : "-") : "X"
+ let R = flags.isReachableFlagSet ? "R" : "-"
+ let c = flags.isConnectionRequiredFlagSet ? "c" : "-"
+ let t = flags.isTransientConnectionFlagSet ? "t" : "-"
+ let i = flags.isInterventionRequiredFlagSet ? "i" : "-"
+ let C = flags.isConnectionOnTrafficFlagSet ? "C" : "-"
+ let D = flags.isConnectionOnDemandFlagSet ? "D" : "-"
+ let l = flags.isLocalAddressFlagSet ? "l" : "-"
+ let d = flags.isDirectFlagSet ? "d" : "-"
+
+ return "\(W)\(R) \(c)\(t)\(i)\(C)\(D)\(l)\(d)"
+ }
+}
+
+fileprivate extension Reachability {
+
+ func setReachabilityFlags() throws {
+ try reachabilitySerialQueue.sync { [unowned self] in
+ var flags = SCNetworkReachabilityFlags()
+ if !SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags) {
+ self.stopNotifier()
+ throw ReachabilityError.UnableToGetInitialFlags
+ }
+
+ self.flags = flags
+ }
+ }
+
+ func reachabilityChanged() {
+ let block = connection != .none ? whenReachable : whenUnreachable
+
+ DispatchQueue.main.async { [weak self] in
+ guard let strongSelf = self else { return }
+ block?(strongSelf)
+ strongSelf.notificationCenter.post(name: .reachabilityChanged, object: strongSelf)
+ }
+ }
+}
+
+extension SCNetworkReachabilityFlags {
+
+ typealias Connection = Reachability.Connection
+
+ var connection: Connection {
+ guard isReachableFlagSet else { return .none }
+
+ // If we're reachable, but not on an iOS device (i.e. simulator), we must be on WiFi
+ #if targetEnvironment(simulator)
+ return .wifi
+ #else
+ var connection = Connection.none
+
+ if !isConnectionRequiredFlagSet {
+ connection = .wifi
+ }
+
+ if isConnectionOnTrafficOrDemandFlagSet {
+ if !isInterventionRequiredFlagSet {
+ connection = .wifi
+ }
+ }
+
+ if isOnWWANFlagSet {
+ connection = .cellular
+ }
+
+ return connection
+ #endif
+ }
+
+ var isOnWWANFlagSet: Bool {
+ #if os(iOS)
+ return contains(.isWWAN)
+ #else
+ return false
+ #endif
+ }
+ var isReachableFlagSet: Bool {
+ return contains(.reachable)
+ }
+ var isConnectionRequiredFlagSet: Bool {
+ return contains(.connectionRequired)
+ }
+ var isInterventionRequiredFlagSet: Bool {
+ return contains(.interventionRequired)
+ }
+ var isConnectionOnTrafficFlagSet: Bool {
+ return contains(.connectionOnTraffic)
+ }
+ var isConnectionOnDemandFlagSet: Bool {
+ return contains(.connectionOnDemand)
+ }
+ var isConnectionOnTrafficOrDemandFlagSet: Bool {
+ return !intersection([.connectionOnTraffic, .connectionOnDemand]).isEmpty
+ }
+ var isTransientConnectionFlagSet: Bool {
+ return contains(.transientConnection)
+ }
+ var isLocalAddressFlagSet: Bool {
+ return contains(.isLocalAddress)
+ }
+ var isDirectFlagSet: Bool {
+ return contains(.isDirect)
+ }
+ var isConnectionRequiredAndTransientFlagSet: Bool {
+ return intersection([.connectionRequired, .transientConnection]) == [.connectionRequired, .transientConnection]
+ }
+}