mirror of
https://github.com/confirmedcode/Lockdown-iOS.git
synced 2025-12-21 12:14:02 +01:00
293 lines
9.4 KiB
Swift
293 lines
9.4 KiB
Swift
|
|
// Copyright (c) 2014, Ashley Mills
|
|
|
|
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.availabilityChanged")
|
|
public let AvailabilityChangedNotification = NSNotification.Name("AvailabilityChangedNotification")
|
|
|
|
public extension Notification.Name {
|
|
static let availabilityChanged = Notification.Name("availabilityChanged")
|
|
}
|
|
|
|
public class Availability {
|
|
|
|
public typealias NetworkReachable = (Availability) -> ()
|
|
public typealias NetworkUnreachable = (Availability) -> ()
|
|
|
|
@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, 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, 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 ref: SCNetworkReachability
|
|
fileprivate let serialQueue: DispatchQueue
|
|
fileprivate(set) var flags: SCNetworkReachabilityFlags? {
|
|
didSet {
|
|
guard flags != oldValue else { return }
|
|
availabilityChanged()
|
|
}
|
|
}
|
|
|
|
required public init(availabilityRef: SCNetworkReachability, queueQoS: DispatchQoS = .default, targetQueue: DispatchQueue? = nil) {
|
|
self.allowsCellularConnection = true
|
|
self.ref = availabilityRef
|
|
self.serialQueue = DispatchQueue(label: "uk.co.ashleymills.availability", 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(availabilityRef: 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(availabilityRef: ref, queueQoS: queueQoS, targetQueue: targetQueue)
|
|
}
|
|
|
|
deinit {
|
|
stopNotifier()
|
|
}
|
|
}
|
|
|
|
public extension Availability {
|
|
|
|
// 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<Availability>.fromOpaque(info).takeUnretainedValue()
|
|
reachability.flags = flags
|
|
}
|
|
|
|
var context = SCNetworkReachabilityContext(version: 0, info: nil, retain: nil, release: nil, copyDescription: nil)
|
|
context.info = UnsafeMutableRawPointer(Unmanaged<Availability>.passUnretained(self).toOpaque())
|
|
if !SCNetworkReachabilitySetCallback(ref, callback, &context) {
|
|
stopNotifier()
|
|
throw ReachabilityError.UnableToSetCallback
|
|
}
|
|
|
|
if !SCNetworkReachabilitySetDispatchQueue(ref, serialQueue) {
|
|
stopNotifier()
|
|
throw ReachabilityError.UnableToSetDispatchQueue
|
|
}
|
|
|
|
// Perform an initial check
|
|
try setReachabilityFlags()
|
|
|
|
notifierRunning = true
|
|
}
|
|
|
|
func stopNotifier() {
|
|
defer { notifierRunning = false }
|
|
|
|
SCNetworkReachabilitySetCallback(ref, nil, nil)
|
|
SCNetworkReachabilitySetDispatchQueue(ref, nil)
|
|
}
|
|
|
|
// MARK: - *** Connection test methods ***
|
|
@available(*, deprecated, message: "Please use `connection != .none`")
|
|
var isReachable: Bool {
|
|
return connection != .none
|
|
}
|
|
|
|
@available(*, deprecated, 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, 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 Availability {
|
|
|
|
func setReachabilityFlags() throws {
|
|
try serialQueue.sync { [unowned self] in
|
|
var flags = SCNetworkReachabilityFlags()
|
|
if !SCNetworkReachabilityGetFlags(self.ref, &flags) {
|
|
self.stopNotifier()
|
|
throw ReachabilityError.UnableToGetInitialFlags
|
|
}
|
|
|
|
self.flags = flags
|
|
}
|
|
}
|
|
|
|
func availabilityChanged() {
|
|
let block = connection != .none ? whenReachable : whenUnreachable
|
|
|
|
DispatchQueue.main.async { [weak self] in
|
|
guard let self = self else { return }
|
|
block?(self)
|
|
self.notificationCenter.post(name: .availabilityChanged, object: self)
|
|
}
|
|
}
|
|
}
|
|
|
|
extension SCNetworkReachabilityFlags {
|
|
|
|
typealias Connection = Availability.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]
|
|
}
|
|
}
|