Swift iOS-当有wifi连接但没有互联网连接时该怎么办?

时间:2017-03-28 10:58:48

标签: ios networking alamofire reachability nsnotifications

我在我的iOS应用中使用Alamofire。我在viewWillAppear和带有NSNotifications的AppDelegate中使用bool值来检查是否存在Internet连接。如果没有wifi连接,则会出现弹出窗口通知用户。如果有wifi连接,弹出窗口消失,一切正常。只要wifi明显不起作用,我就没有问题。

我参加了聚会,有人向我解释说它的工作方式是寻找无线连接,而不是互联网连接。例如,如果我有一个wifi路由器,它插入但路由器没有连接到互联网Alamofire将这视为一个成功的连接,因为它实际上是连接到wifi虽然它不知道无法连接到wifi上网。

我只是在我连接到开放网络的情况下,我的应用程序最初响应,好像我实际连接到互联网(没有弹出窗口),但我无法连接任何东西。 wifi信号已满。在终端我跑了ping,事实证明连接已经死了。我的应用程序无法区分。

enter image description here enter image description here

如何在这样的情况下弹出一个弹出窗口?

还有什么.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()

SomeVC:

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)
}
}

4 个答案:

答案 0 :(得分:1)

您可以使用主机启动NetworkReachabilityManager,例如google host,因为默认为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  
    }

答案 1 :(得分:0)

我按照了这个AshleyMills Reachability文件,它使用google.com来测试连接。您只需将下面的内容复制并粘贴到新文件中即可测试其工作原理。如果您在中国使用此功能,请使用alibaba.com,因为谷歌在中国无法使用。

// everywhere outside China
let hostNames = [nil, "google.com", "invalidhost"]

// everywhere including China
let hostNames = [nil, "alibaba.com", "invalidhost"]

首次启动应用时,您会看到标签每5秒更改一次,因为里面有异步计时器:func startHost(at index: Int)

如果您想要停止并自己控制所有内容,请注释掉以下代码:

// comment this out to stop the connection from changing
DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
    //self.startHost(at: (index + 1) % 3)
}

一旦您发表评论,您应该在Wifi,移动电话连接和飞行模式之间切换电话连接。标签颜色将根据连接从绿色变为蓝色变为红色。

创建一个新项目并将其添加到ViewController文件中:

import UIKit

class ViewController: UIViewController {

    let networkStatus: UILabel = {
        let label = UILabel()
        label.translatesAutoresizingMaskIntoConstraints = false
        label.font = UIFont.systemFont(ofSize: 21)
        label.textColor = .black
        label.numberOfLines = 0
        label.sizeToFit()
        label.text = "Status"
        label.textAlignment = .center
        return label
    }()

    let hostNameLabel: UILabel = {
        let label = UILabel()
        label.translatesAutoresizingMaskIntoConstraints = false
        label.font = UIFont.systemFont(ofSize: 21)
        label.textColor = .black
        label.numberOfLines = 0
        label.sizeToFit()
        label.text = "Host"
        label.textAlignment = .center
        return label
    }()

    var reachability: Reachability?
    let hostNames = [nil, "google.com", "invalidhost"]
    var hostIndex = 0

    override func viewDidLoad() {
        super.viewDidLoad()

        setConstraints()
        startHost(at: 1)
    }

    func startHost(at index: Int) {
        stopNotifier()
        setupReachability(hostNames[index], useClosures: true)
        startNotifier()

        // this loops the connection every 5 seconds
        DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
            self.startHost(at: (index + 1) % 3)
        }
    }

    func setupReachability(_ hostName: String?, useClosures: Bool) {
        let reachability: Reachability?
        if let hostName = hostName {
            reachability = Reachability(hostname: hostName)
            hostNameLabel.text = hostName
        } else {
            reachability = Reachability()
            hostNameLabel.text = "No host name"
        }
        self.reachability = reachability
        print("--- set up with host name: \(hostNameLabel.text!)")

        if useClosures {
            reachability?.whenReachable = { reachability in
                self.updateLabelColourWhenReachable(reachability)
            }
            reachability?.whenUnreachable = { reachability in
                self.updateLabelColourWhenNotReachable(reachability)
            }
        } else {
            NotificationCenter.default.addObserver(
                self,
                selector: #selector(reachabilityChanged(_:)),
                name: .reachabilityChanged,
                object: reachability
            )
        }
    }

    func startNotifier() {
        print("--- start notifier")
        do {
            try reachability?.startNotifier()
        } catch {
            networkStatus.textColor = .red
            networkStatus.text = "Unable to start\nnotifier"
            return
        }
    }

    func stopNotifier() {
        print("--- stop notifier")
        reachability?.stopNotifier()
        NotificationCenter.default.removeObserver(self, name: .reachabilityChanged, object: nil)
        reachability = nil
    }

    func updateLabelColourWhenReachable(_ reachability: Reachability) {
        print("\(reachability.description) - \(reachability.connection)")
        if reachability.connection == .wifi {
            self.networkStatus.textColor = .green
        } else {
            self.networkStatus.textColor = .blue
        }

        self.networkStatus.text = "\(reachability.connection)"
    }

    func updateLabelColourWhenNotReachable(_ reachability: Reachability) {
        print("\(reachability.description) - \(reachability.connection)")

        self.networkStatus.textColor = .red

        self.networkStatus.text = "\(reachability.connection)"
    }

    @objc func reachabilityChanged(_ note: Notification) {
        let reachability = note.object as! Reachability

        if reachability.connection != .none {
            updateLabelColourWhenReachable(reachability)
        } else {
            updateLabelColourWhenNotReachable(reachability)
        }
    }

    func setConstraints(){
        view.addSubview(networkStatus)
        view.addSubview(hostNameLabel)

        networkStatus.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 8).isActive = true
        networkStatus.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -8).isActive = true
        networkStatus.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true

        hostNameLabel.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 8).isActive = true
        hostNameLabel.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -8).isActive = true
        hostNameLabel.topAnchor.constraint(equalTo: networkStatus.bottomAnchor, constant: 20).isActive = true
    }

    deinit {
        stopNotifier()
    }
}

创建名为Reachability的文件并将其添加到其中:

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 }

        let block = connection != .none ? whenReachable : whenUnreachable

        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()
        }
    }
}

如果要在实际项目中使用此项,请将hostNames的数组更改为:

let hostNames = ["google.com"] // or alibaba.com

// move it to viewWillAppear because the reachability class property gets set to nil in stopNotifier when it runs inside deinit
override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)

    startHost(at: 0)
}

// remove the dispatch async timer
func startHost(at index: Int) {
    setupReachability(hostNames[index], useClosures: true)
    startNotifier()
}

// add everything else that's inside the ViewController File above

答案 2 :(得分:0)

以下是由这两个解决方案组成的答案:Pavle MijatovicYasin Ugurlu

创建一个新类并将其命名为Connection并将其下面的代码复制并粘贴到其中:

import Foundation
import SystemConfiguration

class Connection {

    class func isConnectedToNetwork() -> Bool {

        var zeroAddress = sockaddr_in(sin_len: 0, sin_family: 0, sin_port: 0, sin_addr: in_addr(s_addr: 0), sin_zero: (0, 0, 0, 0, 0, 0, 0, 0))
        zeroAddress.sin_len = UInt8(MemoryLayout.size(ofValue: zeroAddress))
        zeroAddress.sin_family = sa_family_t(AF_INET)

        guard let defaultRouteReachability = withUnsafePointer(to: &zeroAddress, {
            $0.withMemoryRebound(to: sockaddr.self, capacity: 1) {
                SCNetworkReachabilityCreateWithAddress(nil, $0)
            }
        }) else {
            return false
        }

        var flags: SCNetworkReachabilityFlags = SCNetworkReachabilityFlags(rawValue: 0)
        if SCNetworkReachabilityGetFlags(defaultRouteReachability, &flags) == false {
            return false
        }

        let isReachable = (flags.rawValue & UInt32(kSCNetworkFlagsReachable)) != 0
        let needsConnection = (flags.rawValue & UInt32(kSCNetworkFlagsConnectionRequired)) != 0

        return isReachable && !needsConnection

    }

    class func isInternetAvailable(webSiteToPing: String?, completionHandler: @escaping (Bool) -> Void) {

        // 1. Check the WiFi Connection
        guard isConnectedToNetwork() else {
            completionHandler(false)
            return
        }

        // 2. Check the Internet Connection but possibly use www.apple.com or www.alibaba.com instead of google.com because it's not available in China
        var webAddress = "https://www.google.com" // Default Web Site
        if let _ = webSiteToPing {
            webAddress = webSiteToPing!
        }

        guard let url = URL(string: webAddress) else {
            completionHandler(false)
            print("could not create url from: \(webAddress)")
            return
        }

        let urlRequest = URLRequest(url: url)
        let session = URLSession.shared
        let task = session.dataTask(with: urlRequest, completionHandler: { (data, response, error) in
            if error != nil || response == nil {
                completionHandler(false)
            } else {
                completionHandler(true)
            }
        })
        task.resume()
    }
}

在任何视图控制器中,你在viewDidLoad里面使用它就像这样调用它:

override func viewDidLoad() {
        super.viewDidLoad()

        // if in china usewww.apple.com or www.alibaba.com instead
        Connection.isInternetAvailable(webSiteToPing: "https://www.google.com", completionHandler: {
            (bool) in

            if bool{
                print("Internet connection is good")
            }else{
                print("No internet connection can be found")
            }
        })
}

我发现的一些事情似乎只能使用一次,因为如果连接可用"Internet connection is good"打印,但如果我进入飞行模式"No internet connection can be found"则不会打印。它没有主动倾听,这让我觉得我必须联系到通知?另一件事是,如果你在中国,那么这将不起作用,因为谷歌在中国不可用。也许更好的网站可以查看www.apple.com或www.alibaba.com,因为它们似乎随处可见。

Connection.isInternetAvailable(webSiteToPing: "https://www.apple.com"...

Connection.isInternetAvailable(webSiteToPing: "https://www.alibaba.com"...

答案 3 :(得分:0)

我也遇到了同样的问题,因此在关于stackoverflow的一些答案的帮助下,我创建了一个代码,该代码简单地异步命中了google.com,如果响应状态为200,则在完成处理程序中返回true。

Swift 4中的代码:

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")
     }