Swift iOS- 有wifi但没有网络怎么办?

Swift iOS- What to do when there is a wifi connection but no internet connection?

我在 iOS 应用程序中使用 Alamofire。我在 viewWillAppear 和带有 NSNotifications 的 AppDelegate 中使用 bool 值来检查是否有互联网连接。如果没有 wifi 连接,则会出现一个弹出窗口通知用户。如果有 wifi 连接,弹出窗口消失,一切正常。只要 wifi 明显无法正常工作,我就没有遇到任何问题。

我在一次聚会上有人向我解释说它的工作方式是寻找 wifi 连接而不是互联网连接。例如,如果我有一个 wifi 路由器并且它已插入但路由器未连接到互联网,Alamofire 会将此视为成功连接,因为它实际上正在连接到 wifi,尽管它不知道 wifi 无法连接上网。

我只是处于连接到开放网络的情况,我的应用程序最初的响应就像我实际上连接到互联网一样(没有弹出窗口),但我无法连接到任何东西。无线网络信号是满的。在终端 I 运行 ping 后发现连接已断开。我的应用无法区分。

如何让弹出窗口出现在这种情况下?

还有什么是 .case 未知的?

Alamofire.Swift:

import Foundation
import Alamofire

open class NetworkManager {

    open static var sharedManager: NetworkReachabilityManager = {

        let reachabilityManager = NetworkReachabilityManager()

        reachabilityManager?.listener = { (status) in

            switch status {

            case .notReachable:
                print("The network is not reachable")
                NotificationCenter.default.post(name: NSNotification.Name(rawValue: "unsuccessful"), object: nil)

            case .unknown : //???????
                print("It is unknown wether the network is reachable")
                //I'm not sure whether to put a Notification for successful or unsuccessful???

            case .reachable(.ethernetOrWiFi):
                print("The network is reachable over the WiFi connection")
                NotificationCenter.default.post(name: NSNotification.Name(rawValue: "successful"), object: nil)

            case .reachable(.wwan):
                print("The network is reachable over the WWAN connection")
                NotificationCenter.default.post(name: NSNotification.Name(rawValue: "successful"), object: nil)
            }
        }

        reachabilityManager?.startListening()
        return reachabilityManager!
    }()
}

AppDelegate:

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {

        NetworkManager.sharedManager.startListening()

一些风投:

override func viewWillAppear() {
        super.viewWillAppear()

        NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(successful), name: "successful", object: nil)

        NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(unsuccessful), name: "unsuccessful", object: nil)

       if NetworkManager.sharedManager.isReachable == true{
            self.successful()
       }else{
            self.unsuccessful()
       }

       if NetworkManager.sharedManager.isReachableOnWWAN == true{ 
            self.successful()
       }else{
            self.unsuccessful()
       }

       if NetworkManager.sharedManager.isReachableOnEthernetOrWiFi == true{ 
            self.successful()
       }else{
            self.unsuccessful()
       }
}

func successful(){
    //dismiss pop up
}

func unsuccessful(){
    //show pop up
}

deinit{
    NSNotificationCenter.defaultCenter().removeObserver(self, name: "successful", object: nil)
    NSNotificationCenter.defaultCenter().removeObserver(self, name: "unsuccessful", object: nil)
}
}

你可以初始化NetworkReachabilityManager主机,例如google主机,因为默认是0.0.0.0

let reachabilityManager = Alamofire.NetworkReachabilityManager(host: "www.google.com")

当您开始监听可达性管理器对主机执行 ping 操作时。如果网络可用,您可以缓存 SSID 并在 SSID 更改时再次 ping。

case .unknown 最好放一个不成功的通知。

示例获取 SSID(它在模拟器中不起作用):

func fetchSSIDInfo() ->  String? {  
        if let interfaces = CNCopySupportedInterfaces() {  
            for i in 0..<CFArrayGetCount(interfaces){  
                let interfaceName: UnsafeRawPointer = CFArrayGetValueAtIndex(interfaces, i)  
                let rec = unsafeBitCast(interfaceName, to: AnyObject.self)  
                let unsafeInterfaceData = CNCopyCurrentNetworkInfo("\(rec)" as CFString)  

                if let unsafeInterfaceData = unsafeInterfaceData as? Dictionary<AnyHashable, Any> {  
                    return unsafeInterfaceData["SSID"] as? String  
                }  
            }  
        }  
        return nil  
    }

我遵循了这个 AshleyMills Reachability 文件(添加在下面),它使用 google.com 来测试连接。

重要的是要注意我设置它的方式是它专门监视 viewWillAppear 并且在切换选项卡和 pushing/popping/presenting 视图控制器时效果最好。在实际的 AshleyMills GitHub 回购中,他使用 DispatchQueue.asyncAfter( .now() + 5) 计时器和其他代码来持续监控,但我的回答中没有包含这些代码。为了持续监控,您应该使用上面 link 中的他的 ViewController 文件或此线程中我的

将此添加到 viewWillAppearviewWillDisappear:

var reachability: Reachability?
let reachabilityConnection = Reachability()!

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
        
    setupReachability("www.google.com") // inside China use www.alibaba.com
}

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)

    stopNotifier()
}

func setupReachability(_ hostName: String) {
    
    self.reachability = Reachability(hostname: hostName)
    
    startNotifier()
}

func startNotifier() {
    do {
        print("notifier started")

        try reachability?.startNotifier()

        monitorReachability()

    } catch {
        print("*****Could not start notifier*****")
    }
}

func monitorReachability() {
    
    reachability?.whenReachable = { [weak self] (_) in
        
        self?.reachabilityChanged()
    }
    reachability?.whenUnreachable = { [weak self] (_) in
        
        self?.reachabilityChanged()
    }
}

func reachabilityChanged() {
    let reachability = reachabilityConnection
    
    switch reachability.connection {
        
    case .wifi:
        print("----Reachable via WiFi")
    case .cellular:
        print("----Reachable via Cellular")
    case .none:
        print("----No Signal")
    }
}

func stopNotifier() {
    
    reachability?.stopNotifier()

    reachability = nil

    print("notifier stopped")
}

为 AshleyMills 文件创建一个文件并将其添加到其中。我将文件命名为 Networkability:

import SystemConfiguration
import Foundation

public enum ReachabilityError: Error {
    case FailedToCreateWithAddress(sockaddr_in)
    case FailedToCreateWithHostname(String)
    case UnableToSetCallback
    case UnableToSetDispatchQueue
}

@available(*, unavailable, renamed: "Notification.Name.reachabilityChanged")
public let ReachabilityChangedNotification = NSNotification.Name("ReachabilityChangedNotification")

extension Notification.Name {
    public static let reachabilityChanged = Notification.Name("reachabilityChanged")
}

func callback(reachability: SCNetworkReachability, flags: SCNetworkReachabilityFlags, info: UnsafeMutableRawPointer?) {
    guard let info = info else { return }
    
    let reachability = Unmanaged<Reachability>.fromOpaque(info).takeUnretainedValue()
    reachability.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 {
        guard isReachableFlagSet else { return .none }
        
        // If we're reachable, but not on an iOS device (i.e. simulator), we must be on WiFi
        guard isRunningOnDevice else { return .wifi }
        
        var connection = Connection.none
        
        if !isConnectionRequiredFlagSet {
            connection = .wifi
        }
        
        if isConnectionOnTrafficOrDemandFlagSet {
            if !isInterventionRequiredFlagSet {
                connection = .wifi
            }
        }
        
        if isOnWWANFlagSet {
            if !allowsCellularConnection {
                connection = .none
            } else {
                connection = .cellular
            }
        }
        
        return connection
    }
    
    fileprivate var previousFlags: SCNetworkReachabilityFlags?
    
    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(label: "uk.co.ashleymills.reachability")
    
    fileprivate var usingHostname = false
    
    required public init(reachabilityRef: SCNetworkReachability, usingHostname: Bool = false) {
        allowsCellularConnection = true
        self.reachabilityRef = reachabilityRef
        self.usingHostname = usingHostname
    }
    
    public convenience init?(hostname: String) {
        guard let ref = SCNetworkReachabilityCreateWithName(nil, hostname) else { return nil }
        self.init(reachabilityRef: ref, usingHostname: true)
    }
    
    public convenience init?() {
        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)
    }
    
    deinit {
        stopNotifier()
    }
}

public extension Reachability {
    
    // MARK: - *** Notifier methods ***
    func startNotifier() throws {
        guard !notifierRunning else { return }
        
        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
        reachabilitySerialQueue.async {
            self.reachabilityChanged()
        }
        
        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 {
        guard isReachableFlagSet else { return false }
        
        if isConnectionRequiredAndTransientFlagSet {
            return false
        }
        
        if isRunningOnDevice {
            if isOnWWANFlagSet && !reachableOnWWAN {
                // We don't want to connect when on cellular connection
                return false
            }
        }
        
        return true
    }
    
    @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 isRunningOnDevice && isReachableFlagSet && isOnWWANFlagSet
    }
    
    @available(*, deprecated: 4.0, message: "Please use `connection == .wifi`")
    var isReachableViaWiFi: Bool {
        // Check we're reachable
        guard isReachableFlagSet else { return false }
        
        // If reachable we're reachable, but not on an iOS device (i.e. simulator), we must be on WiFi
        guard isRunningOnDevice else { return true }
        
        // Check we're NOT on WWAN
        return !isOnWWANFlagSet
    }
    
    var description: String {
        let W = isRunningOnDevice ? (isOnWWANFlagSet ? "W" : "-") : "X"
        let R = isReachableFlagSet ? "R" : "-"
        let c = isConnectionRequiredFlagSet ? "c" : "-"
        let t = isTransientConnectionFlagSet ? "t" : "-"
        let i = isInterventionRequiredFlagSet ? "i" : "-"
        let C = isConnectionOnTrafficFlagSet ? "C" : "-"
        let D = isConnectionOnDemandFlagSet ? "D" : "-"
        let l = isLocalAddressFlagSet ? "l" : "-"
        let d = isDirectFlagSet ? "d" : "-"
        
        return "\(W)\(R) \(c)\(t)\(i)\(C)\(D)\(l)\(d)"
    }
}

fileprivate extension Reachability {
    func reachabilityChanged() {
        guard previousFlags != flags else { return }
        
        guard let reachable = whenReachable else { return }
        guard let unreachable = whenUnreachable else { return }
        print("?????>>>>>>\(reachable)")
        let block = connection != .none ? reachable : unreachable
        
        DispatchQueue.main.async {
            if self.usingHostname {
                print("USING HOSTNAME ABOUT TO CALL BLOCK")
            }
            block(self)
            self.notificationCenter.post(name: .reachabilityChanged, object:self)
        }
        
        previousFlags = flags
    }
    
    var isOnWWANFlagSet: Bool {
        #if os(iOS)
        return flags.contains(.isWWAN)
        #else
        return false
        #endif
    }
    var isReachableFlagSet: Bool {
        return flags.contains(.reachable)
    }
    var isConnectionRequiredFlagSet: Bool {
        return flags.contains(.connectionRequired)
    }
    var isInterventionRequiredFlagSet: Bool {
        return flags.contains(.interventionRequired)
    }
    var isConnectionOnTrafficFlagSet: Bool {
        return flags.contains(.connectionOnTraffic)
    }
    var isConnectionOnDemandFlagSet: Bool {
        return flags.contains(.connectionOnDemand)
    }
    var isConnectionOnTrafficOrDemandFlagSet: Bool {
        return !flags.intersection([.connectionOnTraffic, .connectionOnDemand]).isEmpty
    }
    var isTransientConnectionFlagSet: Bool {
        return flags.contains(.transientConnection)
    }
    var isLocalAddressFlagSet: Bool {
        return flags.contains(.isLocalAddress)
    }
    var isDirectFlagSet: Bool {
        return flags.contains(.isDirect)
    }
    var isConnectionRequiredAndTransientFlagSet: Bool {
        return flags.intersection([.connectionRequired, .transientConnection]) == [.connectionRequired, .transientConnection]
    }
    
    var flags: SCNetworkReachabilityFlags {
        var flags = SCNetworkReachabilityFlags()
        if SCNetworkReachabilityGetFlags(reachabilityRef, &flags) {
            print("Returning flags \(flags)")
            return flags
        } else {
            return SCNetworkReachabilityFlags()
        }
    }
}

这是一个使用 Firebase 的答案。您必须事先 install the Firebase pod。将此添加到 viewWillAppearviewWillDisappear:

import Firebase

let connectedRef = Database.database().reference(withPath: ".info/connected")

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)

    checkForAFirebaseConnection()   
}

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)

    connectedRef.removeAllObservers()
}

func checkForAFirebaseConnection() {

    connectedRef.observe(.value, with: { [weak self](connected) in
        if let boolean = connected.value as? Bool, boolean == true {
            print("Firebase is connected")
        } else {
            print("Firebase is NOT connected")
        }
    })
}

我遇到了同样的问题,所以在 Whosebug 上的一些答案的帮助下,我创建了一个代码,如果响应状态为 200,则简单地异步命中 google.com 并且在完成处理程序中 return 为真。

Swift4 中的代码:

class func checkInternet(showLoader: Bool = true, completionHandler:@escaping (_ internet:Bool) -> Void)
{
    UIApplication.shared.isNetworkActivityIndicatorVisible = true

    let url = URL(string: "http://www.google.com/")
    var req = URLRequest.init(url: url!)
    req.cachePolicy = URLRequest.CachePolicy.reloadIgnoringLocalAndRemoteCacheData
    req.timeoutInterval = 10.0

    if showLoader {
        Loader.startLoading()
    }
    let task = URLSession.shared.dataTask(with: req) { (data, response, error) in

        if showLoader {
            Loader.stopLoading()
        }

        if error != nil  {
            completionHandler(false)
        } else {
            if let httpResponse = response as? HTTPURLResponse {
                if httpResponse.statusCode == 200 {
                    completionHandler(true)
                } else {
                    completionHandler(false)
                }
            } else {
                completionHandler(false)
            }
        }
    }
    task.resume() 
  }

现在您可以像这样使用它:

     InternetCheck.checkInternet(completionHandler: { (available) in
         if available {
              print("Net available")
         } else {
              print("Net not available")
     }