Reachability.swift
import Foundation
import SystemConfiguration
class Reachability {
var hostname: String?
var isRunning = false
var isReachableOnWWAN: Bool
var reachability: SCNetworkReachability?
var reachabilityFlags = SCNetworkReachabilityFlags()
let reachabilitySerialQueue = DispatchQueue(label: "ReachabilityQueue")
init?(hostname: String) throws {
guard let reachability = SCNetworkReachabilityCreateWithName(nil, hostname) else {
throw Network.Error.failedToCreateWith(hostname)
}
self.reachability = reachability
self.hostname = hostname
isReachableOnWWAN = true
}
init?() throws {
var zeroAddress = sockaddr_in()
zeroAddress.sin_len = UInt8(MemoryLayout<sockaddr_in>.size)
zeroAddress.sin_family = sa_family_t(AF_INET)
guard let reachability = withUnsafePointer(to: &zeroAddress, {
$0.withMemoryRebound(to: sockaddr.self, capacity: 1) {
SCNetworkReachabilityCreateWithAddress(nil, $0)
}}) else {
throw Network.Error.failedToInitializeWith(zeroAddress)
}
self.reachability = reachability
isReachableOnWWAN = true
}
var status: Network.Status {
return !isConnectedToNetwork ? .unreachable :
isReachableViaWiFi ? .wifi :
isRunningOnDevice ? .wwan : .unreachable
}
var isRunningOnDevice: Bool = {
#if (arch(i386) || arch(x86_64)) && os(iOS)
return false
#else
return true
#endif
}()
deinit { stop() }
}
extension Reachability {
func start() throws {
guard let reachability = reachability, !isRunning else { return }
var context = SCNetworkReachabilityContext(version: 0, info: nil, retain: nil, release: nil, copyDescription: nil)
context.info = Unmanaged<Reachability>.passUnretained(self).toOpaque()
guard SCNetworkReachabilitySetCallback(reachability, callout, &context) else { stop()
throw Network.Error.failedToSetCallout
}
guard SCNetworkReachabilitySetDispatchQueue(reachability, reachabilitySerialQueue) else { stop()
throw Network.Error.failedToSetDispatchQueue
}
reachabilitySerialQueue.async { self.flagsChanged() }
isRunning = true
}
func stop() {
defer { isRunning = false }
guard let reachability = reachability else { return }
SCNetworkReachabilitySetCallback(reachability, nil, nil)
SCNetworkReachabilitySetDispatchQueue(reachability, nil)
self.reachability = nil
}
var isConnectedToNetwork: Bool {
return isReachable &&
!isConnectionRequiredAndTransientConnection &&
!(isRunningOnDevice && isWWAN && !isReachableOnWWAN)
}
var isReachableViaWiFi: Bool {
return isReachable && isRunningOnDevice && !isWWAN
}
/// 此flags表示网络节点或地址是否可达,包括是否需要链接以及建立连接时是否需要用户干预
var flags: SCNetworkReachabilityFlags? {
guard let reachability = reachability else { return nil }
var flags = SCNetworkReachabilityFlags()
return withUnsafeMutablePointer(to: &flags) {
SCNetworkReachabilityGetFlags(reachability, UnsafeMutablePointer($0))
} ? flags : nil
}
/// 比较当前的flags和之前的flags,如果有改变,发送一个falgsChanged通知
func flagsChanged() {
guard let flags = flags, flags != reachabilityFlags else { return }
reachabilityFlags = flags
NotificationCenter.default.post(name: .flagsChanged, object: self)
}
/// 指定的节点或地址瞬态连接可达,如PPP。
var transientConnection: Bool { return flags?.contains(.transientConnection) == true }
/// 指定的节点或地址使用当前的网络配置连接可达
var isReachable: Bool { return flags?.contains(.reachable) == true }
/// 指定的节点或地址使用当前的网络配置连接可达,但必须先建立连接。 如果此flag被设置,kSCNetworkReachabilityFlagsConnectionOnTraffic flag, kSCNetworkReachabilityFlagsConnectionOnDemand flag
, 或kSCNetworkReachabilityFlagsIsWWAN flag也需要设置来指示必须连接的类型。如果需要手动连接, kSCNetworkReachabilityFlagsInterventionRequired flag 也需要设置.
var connectionRequired: Bool { return flags?.contains(.connectionRequired) == true }
/// 指定的节点或地址使用当前的网络配置连接可达,但必须先建立连接。指向该名称或地址的任何流量将启动连接。
var connectionOnTraffic: Bool { return flags?.contains(.connectionOnTraffic) == true }
/// 指定的节点或地址使用当前的网络配置连接可达,但必须先建立连接。
var interventionRequired: Bool { return flags?.contains(.interventionRequired) == true }
/// 指定的节点或地址使用当前的网络配置连接可达,但必须先建立连接。使用CFSocketStream接口按需建立连接(有关此信息,请参阅CFStream Socket Additions)。
var connectionOnDemand: Bool { return flags?.contains(.connectionOnDemand) == true }
/// 指定节点或地址与当前系统网络接口相关联。
var isLocalAddress: Bool { return flags?.contains(.isLocalAddress) == true }
/// 指定节点或地址的网络流量将不会通过网关,而是直接路由到系统中的接口。
var isDirect: Bool { return flags?.contains(.isDirect) == true }
/// 指定节点或地址4G可达
var isWWAN: Bool { return flags?.contains(.isWWAN) == true }
/// 指定的节点或地址使用当前的网络配置连接可达,但必须先建立连接。如果此标识被设置。
/// 指定节点或地址瞬态连接可达,例如PPP
var isConnectionRequiredAndTransientConnection: Bool {
return (flags?.intersection([.connectionRequired, .transientConnection]) == [.connectionRequired, .transientConnection]) == true
}
}
func callout(reachability: SCNetworkReachability, flags: SCNetworkReachabilityFlags, info: UnsafeMutableRawPointer?) {
guard let info = info else { return }
DispatchQueue.main.async {
Unmanaged<Reachability>.fromOpaque(info).takeUnretainedValue().flagsChanged()
}
}
extension Notification.Name {
static let flagsChanged = Notification.Name("FlagsChanged")
}
struct Network {
static var reachability: Reachability?
enum Status: String, CustomStringConvertible {
case unreachable, wifi, wwan
var description: String { return rawValue }
}
enum Error: Swift.Error {
case failedToSetCallout
case failedToSetDispatchQueue
case failedToCreateWith(String)
case failedToInitializeWith(sockaddr_in)
}
}
使用
在AppDelegate didFinishLaunchingWithOptions方法添加使用
do {
Network.reachability = try Reachability(hostname: "www.google.com")
do {
try Network.reachability?.start()
} catch let error as Network.Error {
print(error)
} catch {
print(error)
}
} catch {
print(error)
}
参考:https://*.com/a/30743763/1304650