ソースを参照

Refactor Reachability (#2915)

* Refactor reachability implementation.

* Additional cleanup and tests.

* Make listener queue private and pass it when listening.

* Protect previous status.
Jon Shier 6 年 前
コミット
052c2c2357

+ 2 - 6
Example/Source/MasterViewController.swift

@@ -90,7 +90,7 @@ class MasterViewController: UITableViewController {
 
     override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
         if indexPath.section == 3 && indexPath.row == 0 {
-            print("Reachability Status: \(reachability.networkReachabilityStatus)")
+            print("Reachability Status: \(reachability.status)")
             tableView.deselectRow(at: indexPath, animated: true)
         }
     }
@@ -98,12 +98,8 @@ class MasterViewController: UITableViewController {
     // MARK: - Private - Reachability
 
     private func monitorReachability() {
-        reachability = NetworkReachabilityManager(host: "www.apple.com")
-
-        reachability.listener = { status in
+        NetworkReachabilityManager.default?.startListening { status in
             print("Reachability Status Changed: \(status)")
         }
-
-        reachability.startListening()
     }
 }

+ 1 - 1
Example/iOS Example.xcodeproj/xcshareddata/xcschemes/iOS Example.xcscheme

@@ -65,7 +65,7 @@
          <EnvironmentVariable
             key = "OS_ACTIVITY_MODE"
             value = "disable"
-            isEnabled = "YES">
+            isEnabled = "NO">
          </EnvironmentVariable>
       </EnvironmentVariables>
       <AdditionalOptions>

+ 138 - 104
Source/NetworkReachabilityManager.swift

@@ -22,12 +22,12 @@
 //  THE SOFTWARE.
 //
 
-#if !os(watchOS)
+#if !(os(watchOS) || os(Linux))
 
 import Foundation
 import SystemConfiguration
 
-/// The `NetworkReachabilityManager` class listens for reachability changes of hosts and addresses for both WWAN and
+/// The `NetworkReachabilityManager` class listens for reachability changes of hosts and addresses for both cellular and
 /// WiFi network interfaces.
 ///
 /// Reachability can be used to determine background information about why a network operation failed, or to retry
@@ -42,65 +42,86 @@ open class NetworkReachabilityManager {
         case notReachable
         /// The network is reachable on the associated `ConnectionType`.
         case reachable(ConnectionType)
-    }
 
-    /// Defines the various connection types detected by reachability flags.
-    public enum ConnectionType {
-        /// The connection type is either over Ethernet or WiFi.
-        case ethernetOrWiFi
-        /// The connection type is a WWAN connection.
-        case wwan
+        init(_ flags: SCNetworkReachabilityFlags) {
+            guard flags.isActuallyReachable else { self = .notReachable; return }
+
+            var networkStatus: NetworkReachabilityStatus = .reachable(.ethernetOrWiFi)
+
+            if flags.isCellular { networkStatus = .reachable(.cellular) }
+
+            self = networkStatus
+        }
+
+        /// Defines the various connection types detected by reachability flags.
+        public enum ConnectionType {
+            /// The connection type is either over Ethernet or WiFi.
+            case ethernetOrWiFi
+            /// The connection type is a cellular connection.
+            case cellular
+        }
     }
 
     /// A closure executed when the network reachability status changes. The closure takes a single argument: the
     /// network reachability status.
     public typealias Listener = (NetworkReachabilityStatus) -> Void
 
+    /// Default `NetworkReachabilityManager` for the zero address and a `listenerQueue` of `.main`.
+    public static let `default` = NetworkReachabilityManager()
+
     // MARK: - Properties
 
     /// Whether the network is currently reachable.
-    open var isReachable: Bool { return isReachableOnWWAN || isReachableOnEthernetOrWiFi }
+    open var isReachable: Bool { return isReachableOnCellular || isReachableOnEthernetOrWiFi }
 
-    /// Whether the network is currently reachable over the WWAN interface.
-    open var isReachableOnWWAN: Bool { return networkReachabilityStatus == .reachable(.wwan) }
+    /// Whether the network is currently reachable over the cellular interface.
+    ///
+    /// - Note: Using this property to decide whether to make a high or low bandwidth request is not recommended.
+    ///         Instead, set the `allowsCellularAccess` on any `URLRequest`s being issued.
+    ///
+    open var isReachableOnCellular: Bool { return status == .reachable(.cellular) }
 
     /// Whether the network is currently reachable over Ethernet or WiFi interface.
-    open var isReachableOnEthernetOrWiFi: Bool { return networkReachabilityStatus == .reachable(.ethernetOrWiFi) }
-
-    /// The current network reachability status.
-    open var networkReachabilityStatus: NetworkReachabilityStatus {
-        guard let flags = self.flags else { return .unknown }
-        return networkReachabilityStatusForFlags(flags)
-    }
+    open var isReachableOnEthernetOrWiFi: Bool { return status == .reachable(.ethernetOrWiFi) }
 
-    /// The dispatch queue to execute the `listener` closure on.
-    open var listenerQueue: DispatchQueue = DispatchQueue.main
-
-    /// A closure executed when the network reachability status changes.
-    open var listener: Listener?
+    /// `DispatchQueue` on which reachability will update.
+    public let reachabilityQueue = DispatchQueue(label: "org.alamofire.reachabilityQueue")
 
     /// Flags of the current reachability type, if any.
     open var flags: SCNetworkReachabilityFlags? {
         var flags = SCNetworkReachabilityFlags()
 
-        if SCNetworkReachabilityGetFlags(reachability, &flags) {
-            return flags
-        }
+        return (SCNetworkReachabilityGetFlags(reachability, &flags)) ? flags : nil
+    }
 
-        return nil
+    /// The current network reachability status.
+    open var status: NetworkReachabilityStatus {
+        return flags.map(NetworkReachabilityStatus.init) ?? .unknown
     }
 
+    /// A closure executed when the network reachability status changes.
+    private var listener: Listener?
+    
+    /// `DispatchQueue` on which listeners will be called.
+    private var listenerQueue: DispatchQueue?
+
+    /// `SCNetworkReachability` instance providing notifications.
     private let reachability: SCNetworkReachability
-    /// Reachability flags of the previous reachability state.
-    open var previousFlags: SCNetworkReachabilityFlags
+    
+    /// Protected storage for the previous status.
+    private let previousStatus = Protector<NetworkReachabilityStatus?>(nil)
 
     // MARK: - Initialization
 
     /// Creates an instance with the specified host.
     ///
-    /// - Parameter host: Host used to evaluate network reachability. Must not include the scheme (i.e. `https`).
+    /// - Note: The `host` value must *not* contain a scheme, just the hostname.
+    ///
+    /// - Parameters:
+    ///   - host:          Host used to evaluate network reachability. Must *not* include the scheme (e.g. `https`).
     public convenience init?(host: String) {
         guard let reachability = SCNetworkReachabilityCreateWithName(nil, host) else { return nil }
+
         self.init(reachability: reachability)
     }
 
@@ -109,24 +130,17 @@ open class NetworkReachabilityManager {
     /// Reachability treats the 0.0.0.0 address as a special token that causes it to monitor the general routing
     /// status of the device, both IPv4 and IPv6.
     public convenience init?() {
-        var address = sockaddr_in()
-        address.sin_len = UInt8(MemoryLayout<sockaddr_in>.size)
-        address.sin_family = sa_family_t(AF_INET)
+        var zero = sockaddr()
+        zero.sa_len = UInt8(MemoryLayout<sockaddr>.size)
+        zero.sa_family = sa_family_t(AF_INET)
 
-        guard let reachability = withUnsafePointer(to: &address, { pointer in
-            return pointer.withMemoryRebound(to: sockaddr.self, capacity: MemoryLayout<sockaddr>.size) {
-                return SCNetworkReachabilityCreateWithAddress(nil, $0)
-            }
-        }) else { return nil }
+        guard let reachability = SCNetworkReachabilityCreateWithAddress(nil, &zero) else { return nil }
 
         self.init(reachability: reachability)
     }
 
     private init(reachability: SCNetworkReachability) {
         self.reachability = reachability
-
-        // Set the previous flags to an unreserved value to represent unknown status
-        self.previousFlags = SCNetworkReachabilityFlags(rawValue: 1 << 30)
     }
 
     deinit {
@@ -137,87 +151,107 @@ open class NetworkReachabilityManager {
 
     /// Starts listening for changes in network reachability status.
     ///
+    /// - Note: Stops and removes any existing listener.
+    ///
+    /// - Parameters:
+    ///   - queue:    `DispatchQueue` on which to call the `listener` closure. `.main` by default.
+    ///   - listener: `Listener` closure called when reachability changes.
+    ///
     /// - Returns: `true` if listening was started successfully, `false` otherwise.
     @discardableResult
-    open func startListening() -> Bool {
-        var context = SCNetworkReachabilityContext(version: 0, info: nil, retain: nil, release: nil, copyDescription: nil)
-        context.info = Unmanaged.passUnretained(self).toOpaque()
-
-        let callbackEnabled = SCNetworkReachabilitySetCallback(
-            reachability,
-            { (_, flags, info) in
-                let reachability = Unmanaged<NetworkReachabilityManager>.fromOpaque(info!).takeUnretainedValue()
-                reachability.notifyListener(flags)
-            },
-            &context
-        )
-
-        let queueEnabled = SCNetworkReachabilitySetDispatchQueue(reachability, listenerQueue)
-
-        listenerQueue.async {
-            guard let flags = self.flags else { return }
-            self.notifyListener(flags)
+    open func startListening(onQueue queue: DispatchQueue = .main,
+                             onUpdatePerforming listener: @escaping Listener) -> Bool {
+        stopListening()
+
+        listenerQueue = queue
+        self.listener = listener
+
+        var context = SCNetworkReachabilityContext(version: 0,
+                                                   info: Unmanaged.passRetained(self).toOpaque(),
+                                                   retain: nil,
+                                                   release: nil,
+                                                   copyDescription: nil)
+        let callback: SCNetworkReachabilityCallBack = { (target, flags, info) in
+            guard let info = info else { return }
+
+            let instance = Unmanaged<NetworkReachabilityManager>.fromOpaque(info).takeUnretainedValue()
+            instance.notifyListener(flags)
         }
 
-        return callbackEnabled && queueEnabled
+        let queueAdded = SCNetworkReachabilitySetDispatchQueue(reachability, reachabilityQueue)
+        let callbackAdded = SCNetworkReachabilitySetCallback(reachability, callback, &context)
+
+        // Manually call listener to give initial state, since the framework may not.
+        if let currentFlags = flags {
+            reachabilityQueue.async {
+                self.notifyListener(currentFlags)
+            }
+        }
+
+        return callbackAdded && queueAdded
     }
 
     /// Stops listening for changes in network reachability status.
     open func stopListening() {
         SCNetworkReachabilitySetCallback(reachability, nil, nil)
         SCNetworkReachabilitySetDispatchQueue(reachability, nil)
+        previousStatus.write { $0 = nil }
+        listenerQueue = nil
+        listener = nil
     }
 
     // MARK: - Internal - Listener Notification
-
+    
+    /// Calls the `listener` closure of the `listenerQueue` if the computed status hasn't changed.
+    ///
+    /// - Note: Should only be called from the `reachabilityQueue`.
+    ///
+    /// - Parameter flags: `SCNetworkReachabilityFlags` to use to calculate the status.
     func notifyListener(_ flags: SCNetworkReachabilityFlags) {
-        guard previousFlags != flags else { return }
-        previousFlags = flags
-
-        listener?(networkReachabilityStatusForFlags(flags))
-    }
-
-    // MARK: - Internal - Network Reachability Status
-
-    func networkReachabilityStatusForFlags(_ flags: SCNetworkReachabilityFlags) -> NetworkReachabilityStatus {
-        guard isNetworkReachable(with: flags) else { return .notReachable }
-
-        var networkStatus: NetworkReachabilityStatus = .reachable(.ethernetOrWiFi)
-
-    #if os(iOS)
-        if flags.contains(.isWWAN) { networkStatus = .reachable(.wwan) }
-    #endif
-
-        return networkStatus
-    }
-
-    func isNetworkReachable(with flags: SCNetworkReachabilityFlags) -> Bool {
-        let isReachable = flags.contains(.reachable)
-        let needsConnection = flags.contains(.connectionRequired)
-        let canConnectAutomatically = flags.contains(.connectionOnDemand) || flags.contains(.connectionOnTraffic)
-        let canConnectWithoutUserInteraction = canConnectAutomatically && !flags.contains(.interventionRequired)
-
-        return isReachable && (!needsConnection || canConnectWithoutUserInteraction)
+        let newStatus = NetworkReachabilityStatus(flags)
+
+        previousStatus.write { previousStatus in
+            guard previousStatus != newStatus else { return }
+            
+            previousStatus = newStatus
+            
+            listenerQueue?.async { self.listener?(newStatus) }
+        }
     }
 }
 
 // MARK: -
 
-extension NetworkReachabilityManager.NetworkReachabilityStatus: Equatable {
-    public static func ==(
-        lhs: NetworkReachabilityManager.NetworkReachabilityStatus,
-        rhs: NetworkReachabilityManager.NetworkReachabilityStatus)
-        -> Bool
-    {
-        switch (lhs, rhs) {
-        case (.unknown, .unknown), (.notReachable, .notReachable):
-            return true
-        case let (.reachable(lhsConnectionType), .reachable(rhsConnectionType)):
-            return lhsConnectionType == rhsConnectionType
-        default:
-            return false
-        }
+extension NetworkReachabilityManager.NetworkReachabilityStatus: Equatable { }
+
+extension SCNetworkReachabilityFlags {
+    var isReachable: Bool { return contains(.reachable) }
+    var isConnectionRequired: Bool { return contains(.connectionRequired) }
+    var canConnectAutomatically: Bool { return contains(.connectionOnDemand) || contains(.connectionOnTraffic) }
+    var canConnectWithoutUserInteraction: Bool { return canConnectAutomatically && !contains(.interventionRequired) }
+    var isActuallyReachable: Bool { return isReachable && (!isConnectionRequired || canConnectWithoutUserInteraction) }
+    var isCellular: Bool {
+        #if os(iOS) || os(tvOS)
+        return contains(.isWWAN)
+        #else
+        return false
+        #endif
     }
-}
 
+    /// Human readable `String` for all states, to help with debugging.
+    var readableDescription: String {
+        let W = isCellular ? "W" : "-"
+        let R = isReachable ? "R" : "-"
+        let c = isConnectionRequired ? "c" : "-"
+        let t = contains(.transientConnection) ? "t" : "-"
+        let i = contains(.interventionRequired) ? "i" : "-"
+        let C = contains(.connectionOnTraffic) ? "C" : "-"
+        let D = contains(.connectionOnDemand) ? "D" : "-"
+        let l = contains(.isLocalAddress) ? "l" : "-"
+        let d = contains(.isDirect) ? "d" : "-"
+        let a = contains(.connectionAutomatic) ? "a" : "-"
+
+        return "\(W)\(R) \(c)\(t)\(i)\(C)\(D)\(l)\(d)\(a)"
+    }
+}
 #endif

+ 1 - 1
Source/RequestInterceptor.swift

@@ -83,7 +83,7 @@ public protocol RequestRetrier {
     ///   - request:    `Request` that failed due to the provided `Error`.
     ///   - session:    `Session` that produced the `Request`.
     ///   - error:      `Error` encountered while executing the `Request`.
-    ///   - completion: Completion closure to be executed when a retry decision has been deterined.
+    ///   - completion: Completion closure to be executed when a retry decision has been determined.
     func retry(_ request: Request, for session: Session, dueTo error: Error, completion: @escaping (RetryResult) -> Void)
 }
 

+ 84 - 50
Tests/NetworkReachabilityManagerTests.swift

@@ -27,7 +27,7 @@ import Foundation
 import SystemConfiguration
 import XCTest
 
-class NetworkReachabilityManagerTestCase: BaseTestCase {
+final class NetworkReachabilityManagerTestCase: BaseTestCase {
 
     // MARK: - Tests - Initialization
 
@@ -52,9 +52,9 @@ class NetworkReachabilityManagerTestCase: BaseTestCase {
         let manager = NetworkReachabilityManager(host: "localhost")
 
         // Then
-        XCTAssertEqual(manager?.networkReachabilityStatus, .reachable(.ethernetOrWiFi))
+        XCTAssertEqual(manager?.status, .reachable(.ethernetOrWiFi))
         XCTAssertEqual(manager?.isReachable, true)
-        XCTAssertEqual(manager?.isReachableOnWWAN, false)
+        XCTAssertEqual(manager?.isReachableOnCellular, false)
         XCTAssertEqual(manager?.isReachableOnEthernetOrWiFi, true)
     }
 
@@ -63,9 +63,9 @@ class NetworkReachabilityManagerTestCase: BaseTestCase {
         let manager = NetworkReachabilityManager(host: "localhost")
 
         // Then
-        XCTAssertEqual(manager?.networkReachabilityStatus, .reachable(.ethernetOrWiFi))
+        XCTAssertEqual(manager?.status, .reachable(.ethernetOrWiFi))
         XCTAssertEqual(manager?.isReachable, true)
-        XCTAssertEqual(manager?.isReachableOnWWAN, false)
+        XCTAssertEqual(manager?.isReachableOnCellular, false)
         XCTAssertEqual(manager?.isReachableOnEthernetOrWiFi, true)
     }
 
@@ -74,12 +74,58 @@ class NetworkReachabilityManagerTestCase: BaseTestCase {
         let manager = NetworkReachabilityManager()
 
         // Then
-        XCTAssertEqual(manager?.networkReachabilityStatus, .reachable(.ethernetOrWiFi))
+        XCTAssertEqual(manager?.status, .reachable(.ethernetOrWiFi))
         XCTAssertEqual(manager?.isReachable, true)
-        XCTAssertEqual(manager?.isReachableOnWWAN, false)
+        XCTAssertEqual(manager?.isReachableOnCellular, false)
         XCTAssertEqual(manager?.isReachableOnEthernetOrWiFi, true)
     }
 
+    func testThatZeroManagerCanBeProperlyRestarted() {
+        // Given
+        let manager = NetworkReachabilityManager()
+        let first = expectation(description: "first listener notified")
+        let second = expectation(description: "second listener notified")
+
+        // When
+        manager?.startListening { (status) in
+            first.fulfill()
+        }
+        wait(for: [first], timeout: timeout)
+
+        manager?.stopListening()
+
+        manager?.startListening { (status) in
+            second.fulfill()
+        }
+        wait(for: [second], timeout: timeout)
+
+        // Then
+        XCTAssertEqual(manager?.status, .reachable(.ethernetOrWiFi))
+    }
+
+    func testThatHostManagerCanBeProperlyRestarted() {
+        // Given
+        let manager = NetworkReachabilityManager(host: "localhost")
+        let first = expectation(description: "first listener notified")
+        let second = expectation(description: "second listener notified")
+
+        // When
+        manager?.startListening { (status) in
+            first.fulfill()
+        }
+        wait(for: [first], timeout: timeout)
+
+        manager?.stopListening()
+
+        manager?.startListening { (status) in
+            second.fulfill()
+        }
+        wait(for: [second], timeout: timeout)
+
+        // Then
+        XCTAssertEqual(manager?.status, .reachable(.ethernetOrWiFi))
+    }
+
     func testThatHostManagerCanBeDeinitialized() {
         // Given
         var manager: NetworkReachabilityManager? = NetworkReachabilityManager(host: "localhost")
@@ -102,7 +148,7 @@ class NetworkReachabilityManagerTestCase: BaseTestCase {
         XCTAssertNil(manager)
     }
 
-    // MARK: - Tests - Listener
+    // MARK: - Listener
 
     func testThatHostManagerIsNotifiedWhenStartListeningIsCalled() {
         // Given
@@ -114,14 +160,12 @@ class NetworkReachabilityManagerTestCase: BaseTestCase {
         let expectation = self.expectation(description: "listener closure should be executed")
         var networkReachabilityStatus: NetworkReachabilityManager.NetworkReachabilityStatus?
 
-        manager.listener = { status in
+        // When
+        manager.startListening { status in
             guard networkReachabilityStatus == nil else { return }
             networkReachabilityStatus = status
             expectation.fulfill()
         }
-
-        // When
-        manager.startListening()
         waitForExpectations(timeout: timeout, handler: nil)
 
         // Then
@@ -135,116 +179,106 @@ class NetworkReachabilityManagerTestCase: BaseTestCase {
 
         var networkReachabilityStatus: NetworkReachabilityManager.NetworkReachabilityStatus?
 
-        manager?.listener = { status in
+        // When
+        manager?.startListening { status in
             networkReachabilityStatus = status
             expectation.fulfill()
         }
-
-        // When
-        manager?.startListening()
         waitForExpectations(timeout: timeout, handler: nil)
 
         // Then
         XCTAssertEqual(networkReachabilityStatus, .reachable(.ethernetOrWiFi))
     }
 
-    // MARK: - Tests - Network Reachability Status
+    // MARK: - NetworkReachabilityStatus
 
-    func testThatManagerReturnsNotReachableStatusWhenReachableFlagIsAbsent() {
+    func testThatStatusIsNotReachableStatusWhenReachableFlagIsAbsent() {
         // Given
-        let manager = NetworkReachabilityManager()
         let flags: SCNetworkReachabilityFlags = [.connectionOnDemand]
 
         // When
-        let networkReachabilityStatus = manager?.networkReachabilityStatusForFlags(flags)
+        let status = NetworkReachabilityManager.NetworkReachabilityStatus(flags)
 
         // Then
-        XCTAssertEqual(networkReachabilityStatus, .notReachable)
+        XCTAssertEqual(status, .notReachable)
     }
 
-    func testThatManagerReturnsNotReachableStatusWhenConnectionIsRequired() {
+    func testThatStatusIsNotReachableStatusWhenConnectionIsRequired() {
         // Given
-        let manager = NetworkReachabilityManager()
         let flags: SCNetworkReachabilityFlags = [.reachable, .connectionRequired]
 
         // When
-        let networkReachabilityStatus = manager?.networkReachabilityStatusForFlags(flags)
+        let status = NetworkReachabilityManager.NetworkReachabilityStatus(flags)
 
         // Then
-        XCTAssertEqual(networkReachabilityStatus, .notReachable)
+        XCTAssertEqual(status, .notReachable)
     }
 
-    func testThatManagerReturnsNotReachableStatusWhenInterventionIsRequired() {
+    func testThatStatusIsNotReachableStatusWhenInterventionIsRequired() {
         // Given
-        let manager = NetworkReachabilityManager()
         let flags: SCNetworkReachabilityFlags = [.reachable, .connectionRequired, .interventionRequired]
 
         // When
-        let networkReachabilityStatus = manager?.networkReachabilityStatusForFlags(flags)
+        let status = NetworkReachabilityManager.NetworkReachabilityStatus(flags)
 
         // Then
-        XCTAssertEqual(networkReachabilityStatus, .notReachable)
+        XCTAssertEqual(status, .notReachable)
     }
 
-    func testThatManagerReturnsReachableOnWiFiStatusWhenConnectionIsNotRequired() {
+    func testThatStatusIsReachableOnWiFiStatusWhenConnectionIsNotRequired() {
         // Given
-        let manager = NetworkReachabilityManager()
         let flags: SCNetworkReachabilityFlags = [.reachable]
 
         // When
-        let networkReachabilityStatus = manager?.networkReachabilityStatusForFlags(flags)
+        let status = NetworkReachabilityManager.NetworkReachabilityStatus(flags)
 
         // Then
-        XCTAssertEqual(networkReachabilityStatus, .reachable(.ethernetOrWiFi))
+        XCTAssertEqual(status, .reachable(.ethernetOrWiFi))
     }
 
-    func testThatManagerReturnsReachableOnWiFiStatusWhenConnectionIsOnDemand() {
+    func testThatStatusIsReachableOnWiFiStatusWhenConnectionIsOnDemand() {
         // Given
-        let manager = NetworkReachabilityManager()
         let flags: SCNetworkReachabilityFlags = [.reachable, .connectionRequired, .connectionOnDemand]
 
         // When
-        let networkReachabilityStatus = manager?.networkReachabilityStatusForFlags(flags)
+        let status = NetworkReachabilityManager.NetworkReachabilityStatus(flags)
 
         // Then
-        XCTAssertEqual(networkReachabilityStatus, .reachable(.ethernetOrWiFi))
+        XCTAssertEqual(status, .reachable(.ethernetOrWiFi))
     }
 
-    func testThatManagerReturnsReachableOnWiFiStatusWhenConnectionIsOnTraffic() {
+    func testThatStatusIsReachableOnWiFiStatusWhenConnectionIsOnTraffic() {
         // Given
-        let manager = NetworkReachabilityManager()
         let flags: SCNetworkReachabilityFlags = [.reachable, .connectionRequired, .connectionOnTraffic]
 
         // When
-        let networkReachabilityStatus = manager?.networkReachabilityStatusForFlags(flags)
+        let status = NetworkReachabilityManager.NetworkReachabilityStatus(flags)
 
         // Then
-        XCTAssertEqual(networkReachabilityStatus, .reachable(.ethernetOrWiFi))
+        XCTAssertEqual(status, .reachable(.ethernetOrWiFi))
     }
 
-#if os(iOS)
-    func testThatManagerReturnsReachableOnWWANStatusWhenIsWWAN() {
+#if os(iOS) || os(tvOS)
+    func testThatStatusIsReachableOnCellularStatusWhenIsWWAN() {
         // Given
-        let manager = NetworkReachabilityManager()
         let flags: SCNetworkReachabilityFlags = [.reachable, .isWWAN]
 
         // When
-        let networkReachabilityStatus = manager?.networkReachabilityStatusForFlags(flags)
+        let status = NetworkReachabilityManager.NetworkReachabilityStatus(flags)
 
         // Then
-        XCTAssertEqual(networkReachabilityStatus, .reachable(.wwan))
+        XCTAssertEqual(status, .reachable(.cellular))
     }
 
-    func testThatManagerReturnsNotReachableOnWWANStatusWhenIsWWANAndConnectionIsRequired() {
+    func testThatStatusIsNotReachableOnCellularStatusWhenIsWWANAndConnectionIsRequired() {
         // Given
-        let manager = NetworkReachabilityManager()
         let flags: SCNetworkReachabilityFlags = [.reachable, .isWWAN, .connectionRequired]
 
         // When
-        let networkReachabilityStatus = manager?.networkReachabilityStatusForFlags(flags)
+        let status = NetworkReachabilityManager.NetworkReachabilityStatus(flags)
 
         // Then
-        XCTAssertEqual(networkReachabilityStatus, .notReachable)
+        XCTAssertEqual(status, .notReachable)
     }
 #endif
 }