-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 5156c25
Showing
11 changed files
with
689 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
.DS_Store | ||
xcuserdata |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
8 changes: 8 additions & 0 deletions
8
ConnectivityKit.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | ||
<plist version="1.0"> | ||
<dict> | ||
<key>IDEDidComputeMac32BitWarning</key> | ||
<true/> | ||
</dict> | ||
</plist> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
// swift-tools-version:5.5 | ||
// The swift-tools-version declares the minimum version of Swift required to build this package. | ||
|
||
import PackageDescription | ||
|
||
let package = Package( | ||
name: "ConnectivityKit", | ||
platforms: [ | ||
.iOS(.v10), | ||
.macOS(.v10_14), | ||
.tvOS(.v12), | ||
.watchOS(.v6) | ||
], | ||
products: [ | ||
.library( | ||
name: "ConnectivityKit", | ||
targets: ["ConnectivityKit"]), | ||
], | ||
dependencies: [], | ||
targets: [ | ||
.target( | ||
name: "ConnectivityKit", | ||
dependencies: ["Reachability"], | ||
path: "Sources/ConnectivityKit"), | ||
.target( | ||
name: "Reachability", | ||
dependencies: [], | ||
path: "Sources/Reachability", | ||
publicHeadersPath: "" | ||
), | ||
.testTarget( | ||
name: "ConnectivityKitTests", | ||
dependencies: ["ConnectivityKit"]), | ||
] | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
# ConnectivityKit | ||
|
||
Adapting to changes in network connectivity can allow for suspending or resuming network activity. When entering an elevator or going through a tunnel our devices typically lose connectivity completely. We also move out of range of WiFi and transition to a cellular connection. Apple's [Network Framework] includes [NWPathMonitor] which provides updates in response to changes to [NWPath]. This framework was introduced in 2018 and is a modern replacement to [SCNetworkReachability], simply known as the Reachability API which does not support many of the features supported by modern network protocols such as WiFi 6 and 5G. | ||
|
||
For apps which still support older OS versions it is necessary to use Reachability while most users are able to use the Network framework. This package automatically users the API which is available based on the OS version. | ||
|
||
See: [Introduction to Network.framework] | ||
|
||
## Usage | ||
|
||
This package includes `ConnectivityMonitor` which internally uses `NetworkMonitor` or `ReachabilityMonitor` which are simply available as `AnyConnectivityMonitor`. For recent OS versions of iOS, macOS, tvOS and watchOS the `NetworkMonitor` will be used. For earlier versions `ReachabilityMonitor` will be used. | ||
|
||
Simply call the `start` function and provide a path handler to get updates. Call the `cancel` function to discontinue monitoring. | ||
|
||
## Swift Package | ||
|
||
This project is set up as a Swift Package which can be used by the [Swift Package Manager] (SPM) with Xcode. In your `Package.swift` add this package with the code below. | ||
|
||
```swift | ||
dependencies: [ | ||
.package(url: "https://github.com/brennanMKE/ConnectivityKit", from: "1.0.0"), | ||
], | ||
``` | ||
|
||
## Supporting iOS 10.0 and Later | ||
|
||
Since this package automatically handles the selection of the implementation your code can just use `ConnectivityMonitor` to get updates to the network path. The Reachability API comes from the [System Configuration Framework] which is available for all of Apple's platforms except watchOS. The implementation for the `ReachabilityMonitor` will get an empty implementation for watchOS prior to watchOS 6.0 which is when [Network Framework] was first made available to watchOS. | ||
|
||
If your Deployment Target for any of Apple's platforms supports [Network Framework] then it will always use the modern implementation. This package will allow you to use the same code across all platforms and respond to changes to network connectivity. | ||
|
||
--- | ||
[Network Framework]: https://developer.apple.com/documentation/network | ||
[NWPathMonitor]: https://developer.apple.com/documentation/network/nwpathmonitor | ||
[NWPath]: https://developer.apple.com/documentation/network/nwpath | ||
[SCNetworkReachability]: https://developer.apple.com/documentation/systemconfiguration/scnetworkreachability-g7d | ||
[System Configuration Framework]: https://developer.apple.com/documentation/systemconfiguration | ||
[Introduction to Network.framework]: https://developer.apple.com/videos/play/wwdc2018/715 | ||
[Swift Package Manager]: https://swift.org/package-manager/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
import Foundation | ||
|
||
public enum ConnectivityStatus: String { | ||
case satisfied | ||
case unsatisfied | ||
case requiresConnection | ||
} | ||
|
||
extension ConnectivityStatus: CustomStringConvertible { | ||
public var description: String { | ||
rawValue | ||
} | ||
} | ||
|
||
public enum ConnectivityInterfaceType: String { | ||
case other | ||
case wifi | ||
case cellular | ||
case wiredEthernet | ||
case loopback | ||
} | ||
|
||
public struct ConnectivityInterface { | ||
public let name: String | ||
public let type: ConnectivityInterfaceType | ||
} | ||
|
||
extension ConnectivityInterface: CustomStringConvertible { | ||
public var description: String { | ||
"\(name) (\(type))" | ||
} | ||
} | ||
|
||
extension Array where Element == ConnectivityInterface { | ||
public var description: String { | ||
self.map { "\($0.name) (\($0.type))" }.joined(separator: ", ") | ||
} | ||
} | ||
|
||
public struct ConnectivityPath { | ||
public let status: ConnectivityStatus | ||
public let availableInterfaces: [ConnectivityInterface] | ||
public let isExpensive: Bool | ||
public let supportsDNS: Bool | ||
public let supportsIPv4: Bool | ||
public let supportsIPv6: Bool | ||
} | ||
|
||
extension ConnectivityPath: CustomStringConvertible { | ||
public var description: String { | ||
[ | ||
"\(status): \(availableInterfaces.description)", | ||
"Expensive = \(isExpensive ? "YES" : "NO")", | ||
"DNS = \(supportsDNS ? "YES" : "NO")", | ||
"IPv4 = \(supportsIPv4 ? "YES" : "NO")", | ||
"IPv6 = \(supportsIPv6 ? "YES" : "NO")" | ||
].joined(separator: "; ") | ||
} | ||
} | ||
|
||
public typealias PathUpdateHandler = (ConnectivityPath) -> Void | ||
|
||
public protocol AnyConnectivityMonitor { | ||
func start(pathUpdateQueue: DispatchQueue, pathUpdateHandler: @escaping PathUpdateHandler) | ||
func cancel() | ||
} | ||
|
||
public class ConnectivityMonitor { | ||
private var monitor: AnyConnectivityMonitor? | ||
private let queue = DispatchQueue(label: "com.acme.connectivity", qos: .background) | ||
|
||
public init() {} | ||
|
||
public func start(pathUpdateQueue: DispatchQueue, pathUpdateHandler: @escaping PathUpdateHandler) { | ||
let monitor = createMonitor() | ||
monitor.start(pathUpdateQueue: pathUpdateQueue, pathUpdateHandler: pathUpdateHandler) | ||
self.monitor = monitor | ||
} | ||
|
||
public func cancel() { | ||
guard let monitor = monitor else { return } | ||
monitor.cancel() | ||
self.monitor = nil | ||
} | ||
|
||
private func createMonitor() -> AnyConnectivityMonitor { | ||
let result: AnyConnectivityMonitor | ||
if #available(iOS 12.0, macOS 10.14, tvOS 12.0, watchOS 6.0, *) { | ||
result = NetworkMonitor() | ||
} else { | ||
result = ReachabilityMonitor() | ||
} | ||
return result | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
import Foundation | ||
import Network | ||
|
||
@available(iOS 12.0, macOS 10.14, tvOS 12.0, watchOS 6.0, *) | ||
extension ConnectivityStatus { | ||
init(status: NWPath.Status) { | ||
switch status { | ||
case .satisfied: | ||
self = .satisfied | ||
case .unsatisfied: | ||
self = .unsatisfied | ||
case .requiresConnection: | ||
self = .requiresConnection | ||
@unknown default: | ||
self = .unsatisfied | ||
} | ||
} | ||
} | ||
|
||
@available(iOS 12.0, macOS 10.14, tvOS 12.0, watchOS 6.0, *) | ||
extension ConnectivityInterfaceType { | ||
init(interfaceType: NWInterface.InterfaceType) { | ||
switch interfaceType { | ||
case .other: | ||
self = .other | ||
case .wifi: | ||
self = .wifi | ||
case .cellular: | ||
self = .cellular | ||
case .wiredEthernet: | ||
self = .wiredEthernet | ||
case .loopback: | ||
self = .loopback | ||
@unknown default: | ||
self = .other | ||
} | ||
} | ||
} | ||
|
||
@available(iOS 12.0, macOS 10.14, tvOS 12.0, watchOS 6.0, *) | ||
extension ConnectivityInterface { | ||
init(interface: NWInterface) { | ||
name = interface.name | ||
type = ConnectivityInterfaceType(interfaceType: interface.type) | ||
} | ||
} | ||
|
||
@available(iOS 12.0, macOS 10.14, tvOS 12.0, watchOS 6.0, *) | ||
extension ConnectivityPath { | ||
init(path: NWPath) { | ||
status = ConnectivityStatus(status: path.status) | ||
availableInterfaces = path.availableInterfaces.map { ConnectivityInterface(interface: $0) } | ||
isExpensive = path.isExpensive | ||
supportsDNS = path.supportsDNS | ||
supportsIPv4 = path.supportsIPv4 | ||
supportsIPv6 = path.supportsIPv6 | ||
} | ||
} | ||
|
||
@available(iOS 12.0, macOS 10.14, tvOS 12.0, watchOS 6.0, *) | ||
class NetworkMonitor: AnyConnectivityMonitor { | ||
private var monitor: NWPathMonitor? | ||
private var pathUpdateQueue: DispatchQueue? | ||
private var pathUpdateHandler: PathUpdateHandler? | ||
|
||
private let queue = DispatchQueue(label: "com.acme.connectivity.network-monitor", qos: .background) | ||
|
||
@available(iOS 12.0, macOS 10.14, tvOS 12.0, watchOS 6.0, *) | ||
func start(pathUpdateQueue: DispatchQueue, pathUpdateHandler: @escaping PathUpdateHandler) { | ||
self.pathUpdateQueue = pathUpdateQueue | ||
self.pathUpdateHandler = pathUpdateHandler | ||
// A new instance is required each time a monitor is started | ||
let monitor = NWPathMonitor() | ||
monitor.pathUpdateHandler = didUpdate(path:) | ||
monitor.start(queue: queue) | ||
self.monitor = monitor | ||
} | ||
|
||
@available(iOS 12.0, macOS 10.14, tvOS 12.0, watchOS 6.0, *) | ||
func cancel() { | ||
guard let monitor = monitor else { return } | ||
monitor.cancel() | ||
} | ||
|
||
@available(iOS 12.0, macOS 10.14, tvOS 12.0, watchOS 6.0, *) | ||
func didUpdate(path: NWPath) { | ||
guard let pathUpdateHandler = pathUpdateHandler, | ||
let pathUpdateQueue = pathUpdateQueue else { return } | ||
let connectivityPath = ConnectivityPath(path: path) | ||
pathUpdateQueue.async { | ||
pathUpdateHandler(connectivityPath) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
import Foundation | ||
import Reachability | ||
|
||
extension ConnectivityStatus { | ||
|
||
init(networkStatus: NetworkStatus) { | ||
switch networkStatus { | ||
case .notReachable: | ||
self = .unsatisfied | ||
case .reachableViaWiFi, .reachableViaWWAN: | ||
self = .satisfied | ||
@unknown default: | ||
self = .unsatisfied | ||
} | ||
} | ||
} | ||
|
||
extension ConnectivityPath { | ||
|
||
init(networkStatus: NetworkStatus, connectionRequired: Bool) { | ||
status = ConnectivityStatus(networkStatus: networkStatus) | ||
availableInterfaces = [] | ||
isExpensive = networkStatus == .reachableViaWWAN | ||
supportsDNS = false | ||
supportsIPv4 = false | ||
supportsIPv6 = false | ||
} | ||
} | ||
|
||
public class ReachabilityMonitor: AnyConnectivityMonitor { | ||
var reachability: Reachability? | ||
private var pathUpdateQueue: DispatchQueue? | ||
private var pathUpdateHandler: PathUpdateHandler? | ||
|
||
var hostname: String { | ||
let result = Bundle.main.infoDictionary?["ReachabilityHostname"] as? String ?? "github.com" | ||
return result | ||
} | ||
|
||
public func start(pathUpdateQueue: DispatchQueue, pathUpdateHandler: @escaping PathUpdateHandler) { | ||
debugPrint(#function) | ||
self.pathUpdateQueue = pathUpdateQueue | ||
self.pathUpdateHandler = pathUpdateHandler | ||
let reachability = Reachability(hostname: hostname) | ||
reachability.didChangeHandler = didChange(reachability:) | ||
reachability.start() | ||
self.reachability = reachability | ||
} | ||
|
||
public func cancel() { | ||
debugPrint(#function) | ||
guard let reachability = reachability else { return } | ||
reachability.cancel() | ||
self.reachability = nil | ||
} | ||
|
||
func didChange(reachability: Reachability) { | ||
debugPrint(#function) | ||
guard let pathUpdateHandler = pathUpdateHandler, | ||
let pathUpdateQueue = pathUpdateQueue else { return } | ||
let connectivityPath = ConnectivityPath(networkStatus: reachability.currentStatus, connectionRequired: reachability.connectionRequired) | ||
debugPrint("\(connectivityPath)") | ||
pathUpdateQueue.async { | ||
pathUpdateHandler(connectivityPath) | ||
} | ||
} | ||
} |
Oops, something went wrong.