在Swift中检查互联网连接的可用性

109

有没有一种使用Swift检查互联网连接是否可用的方法?

我知道有许多第三方库可以做到这一点,但它们都是用Objective-C编写的。我正在寻找一种Swift替代方案。


5
Swift的一个巨大优势是它与Objective-C(及其库)集成良好。 - user2864740
确实。您使用现有库时遇到了什么问题?从Swift使用Objective C库非常简单,如果您无法做到这一点,编写任何类型的Swift应用程序将变得极为困难。 - Matt Gibson
1
@MattGibson 几乎所有你在 ObjC 中能做的事情都可以用 Swift 相对容易地完成。当然,在这种情况下可能会有一些额外的代码行,但仍然远非“极其困难”。 - Byron Coetsee
@ByronCoetsee 我是指包括使用AppKit等库——我的观点是,为了在Swift中编写任何有用的东西,您需要知道如何与Objective C库进行交互。 - Matt Gibson
请查看alamofire的答案 - https://dev59.com/gF0a5IYBdhLWcg3wD1Lb#46562290 - Jack
https://dev59.com/-VgR5IYBdhLWcg3wbs2X#42710600 - MAhipal Singh
11个回答

231

正如评论中提到的那样,虽然在Swift中可以使用Objective-C库,但我想要一个更纯粹的Swift解决方案。现有的苹果Reachability类和其他第三方库似乎对我来说过于复杂,难以翻译成Swift。我继续搜索,发现了这篇文章,其中介绍了一种检查网络可用性的简单方法。我开始将其翻译成Swift。我遇到了许多问题,但由于来自StackOverflow的Martin R的帮助,我成功地解决了它们,并最终得到了一个可行的Swift解决方案。以下是代码。

import Foundation
import SystemConfiguration

public class Reachability {

    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(sizeofValue(zeroAddress))
        zeroAddress.sin_family = sa_family_t(AF_INET)

        let defaultRouteReachability = withUnsafePointer(&zeroAddress) {
            SCNetworkReachabilityCreateWithAddress(nil, UnsafePointer($0)).takeRetainedValue()
        }

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

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

        return isReachable && !needsConnection
    }

}

对于 Swift > 3.0 版本

public class Reachability {
    public func isConnectedToNetwork() -> Bool {
        var zeroAddress = sockaddr_in()
        zeroAddress.sin_len = UInt8(MemoryLayout<sockaddr_in>.size)
        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 = []
        if !SCNetworkReachabilityGetFlags(defaultRouteReachability, &flags) {
            return false
        }
        if flags.isEmpty {
            return false
        }

        let isReachable = flags.contains(.reachable)
        let needsConnection = flags.contains(.connectionRequired)

        return (isReachable && !needsConnection)
    }
}

这适用于3G和WiFi连接。我还将其上传到我的GitHub并提供了一个可工作的示例。


7
谢谢您的迅速回复(无恶意)。MIT或Apache将是理想选择 - 谢谢! - Andrew Ebling
4
完成了。已更改为MIT。 - Isuru
13
我发现了另一个问题,如果打开了3G,但我没有更多的数据容量,那么isConnectedToNetwork会返回true,但我无法调用我的网络服务。 - János
11
如@Isuru所说,这是基于https://dev59.com/Xl8e5IYBdhLWcg3wja_5#25623647的,该网页已更新以适用于Swift 2。 - Martin R
2
@János:使用Swift 2现在可以设置通知回调,请参见更新的答案https://dev59.com/f4Xca4cB1Zd3GeqPJYWH#27142665。 - Martin R
显示剩余15条评论

15

对于Swift 3.1(iOS 10.1):

如果您想区分网络类型(即WiFi或WWAN):

您可以使用:

func checkWiFi() -> Bool {

    let networkStatus = Reachability().connectionStatus()
    switch networkStatus {
    case .Unknown, .Offline:
        return false
    case .Online(.WWAN):
        print("Connected via WWAN")
        return true
    case .Online(.WiFi):
        print("Connected via WiFi")
        return true
    }
}

这里是整个可达性分类(Reachability-Class),可以区分网络类型:

import Foundation
import SystemConfiguration

import UIKit
import SystemConfiguration.CaptiveNetwork

public let ReachabilityStatusChangedNotification = "ReachabilityStatusChangedNotification"

public enum ReachabilityType: CustomStringConvertible {
    case WWAN
    case WiFi

    public var description: String {
        switch self {
        case .WWAN: return "WWAN"
        case .WiFi: return "WiFi"
        }
    }
}

public enum ReachabilityStatus: CustomStringConvertible  {
    case Offline
    case Online(ReachabilityType)
    case Unknown

    public var description: String {
        switch self {
        case .Offline: return "Offline"
        case .Online(let type): return "Online (\(type))"
        case .Unknown: return "Unknown"
        }
    }
}

public class Reachability {

    func connectionStatus() -> ReachabilityStatus {
        var zeroAddress = sockaddr_in()
        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) { zeroSockAddress in
                SCNetworkReachabilityCreateWithAddress(nil, zeroSockAddress)
            }
        }) else {
           return .Unknown
        }

        var flags : SCNetworkReachabilityFlags = []
        if !SCNetworkReachabilityGetFlags(defaultRouteReachability, &flags) {
            return .Unknown
        }

        return ReachabilityStatus(reachabilityFlags: flags)
    }

    func monitorReachabilityChanges() {
        let host = "google.com"
        var context = SCNetworkReachabilityContext(version: 0, info: nil, retain: nil, release: nil, copyDescription: nil)
        let reachability = SCNetworkReachabilityCreateWithName(nil, host)!

        SCNetworkReachabilitySetCallback(reachability, { (_, flags, _) in
            let status = ReachabilityStatus(reachabilityFlags: flags)

            NotificationCenter.default.post(name: NSNotification.Name(rawValue: ReachabilityStatusChangedNotification), object: nil, userInfo: ["Status": status.description])}, &context)

        SCNetworkReachabilityScheduleWithRunLoop(reachability, CFRunLoopGetMain(), CFRunLoopMode.commonModes.rawValue)
    }
}

extension ReachabilityStatus {

    public init(reachabilityFlags flags: SCNetworkReachabilityFlags) {
        let connectionRequired = flags.contains(.connectionRequired)
        let isReachable = flags.contains(.reachable)
        let isWWAN = flags.contains(.isWWAN)

        if !connectionRequired && isReachable {
            if isWWAN {
                self = .Online(.WWAN)
            } else {
                self = .Online(.WiFi)
            }
        } else {
            self =  .Offline
        }
    }
}

截至2016年12月3日,iOS 10和Swift 3.1都能够良好运行,谢谢! - jjjjjjjj
你好,如果我们想要区分Wifi/3G/移动数据/4G连接,我们该如何识别呢? - user5886755

14

我给你更好的方法...

您必须使用此代码创建一个类

 import Foundation
 public class Reachability {

class func isConnectedToNetwork()->Bool{

    var Status:Bool = false
    let url = NSURL(string: "http://google.com/")
    let request = NSMutableURLRequest(URL: url!)
    request.HTTPMethod = "HEAD"
    request.cachePolicy = NSURLRequestCachePolicy.ReloadIgnoringLocalAndRemoteCacheData
    request.timeoutInterval = 10.0

    var response: NSURLResponse?

    var data = NSURLConnection.sendSynchronousRequest(request, returningResponse: &response, error: nil) as NSData?

    if let httpResponse = response as? NSHTTPURLResponse {
        if httpResponse.statusCode == 200 {
            Status = true
        }
    }

    return Status
  }
}

然后,您可以使用此代码在项目中的任何位置检查 Internet 连接:

if Reachability.isConnectedToNetwork() == true {
     println("Internet connection OK")
} else {
     println("Internet connection FAILED")
}

非常简单!

*此方法基于Vikram Pote的回答!


20
请注意,您不应该使用这种方法,而应该使用上面提到的方法。在需要持续连接的应用程序中,像这种方法一样的响应检查会导致应用程序在网络连接差的情况下(例如在Edge/GPRS网络下)出现挂起现象。请不要使用这种解决方案! - Imran Ahmed
8
1)每次需要向服务器发出额外的请求。 2)http://google.com/也可能无法访问。 - Vijay Singh Rana
2
  1. 是的,这种方式每次需要额外向服务器发送请求,但它可以百分之百地保证互联网可用性...有时设备连接到WiFi网络但无法访问互联网!在这种情况下,这种方式确定了无法访问互联网...其他方式则不行!
  2. 您可以使用自己的服务器代替google.com...这最初是指...
- Dmitry
1
糟糕的解决方案,连接终结者。 - gokhanakkurt
3
gokhanakkurt,请提供另一种解决方案,以确保互联网以100%的效率运行。 - Dmitry
显示剩余2条评论

7

SWIFT 3: 检查wifi互联网连接:

import Foundation
import SystemConfiguration

public class Reachability {
    public func isConnectedToNetwork() -> Bool {
        var zeroAddress = sockaddr_in()
        zeroAddress.sin_len = UInt8(MemoryLayout<sockaddr_in>.size)
        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 = []
        if !SCNetworkReachabilityGetFlags(defaultRouteReachability, &flags) {
            return false
        }

        let isReachable = flags.contains(.reachable)
        let needsConnection = flags.contains(.connectionRequired)

        return (isReachable && !needsConnection)
    }
}

使用方法:

if Reachability.isConnectedToNetwork() == true {
    print("Connected to the internet")
    //  Do something
} else {
    print("No internet connection")
    //  Do something
}

2
你的使用情况需要将 public func isConnectedToNetwork() {...} 更改为 class func isConnectedToNetwork{...} - keverly

5

由于sendSynchronousRequest已经被弃用,我尝试了这个方法,但是在响应结束之前就调用了'return Status'。

不过这个答案很有效,使用Swift检查互联网连接

以下是我尝试的内容:

import Foundation

public class Reachability {

    class func isConnectedToNetwork()->Bool{

        var Status:Bool = false
        let url = NSURL(string: "http://google.com/")
        let request = NSMutableURLRequest(URL: url!)
        request.HTTPMethod = "HEAD"
        request.cachePolicy = NSURLRequestCachePolicy.ReloadIgnoringLocalAndRemoteCacheData
        request.timeoutInterval = 10.0
        let session = NSURLSession.sharedSession()

        session.dataTaskWithRequest(request, completionHandler: {(data, response, error) in
            print("data \(data)")
            print("response \(response)")
            print("error \(error)")

            if let httpResponse = response as? NSHTTPURLResponse {
                print("httpResponse.statusCode \(httpResponse.statusCode)")
                if httpResponse.statusCode == 200 {
                    Status = true
                }
            }

        }).resume()


        return Status
    }
}

我喜欢并使用了你的解决方案。但是我将它与这个答案结合起来:https://dev59.com/eVwY5IYBdhLWcg3wJk8q#34591379,即我添加了一个信号量..所以我等待任务完成。 - Bjqn

4
您也可以使用以下答案。
    func checkInternet(flag:Bool, completionHandler:(internet:Bool) -> Void)
    {
      UIApplication.sharedApplication().networkActivityIndicatorVisible = true

      let url = NSURL(string: "http://www.google.com/")
      let request = NSMutableURLRequest(URL: url!)

      request.HTTPMethod = "HEAD"
      request.cachePolicy = NSURLRequestCachePolicy.ReloadIgnoringLocalAndRemoteCacheData
      request.timeoutInterval = 10.0

      NSURLConnection.sendAsynchronousRequest(request, queue:NSOperationQueue.mainQueue(), completionHandler:
      {(response: NSURLResponse!, data: NSData!, error: NSError!) -> Void in

        UIApplication.sharedApplication().networkActivityIndicatorVisible = false

        let rsp = response as NSHTTPURLResponse?

        completionHandler(internet:rsp?.statusCode == 200)
    })
    }

     func yourMethod()
    {
    self.checkInternet(false, completionHandler:
    {(internet:Bool) -> Void in

        if (internet)
        {
            // "Internet" mean Google
        }
        else
        {
            // No "Internet" no Google
        }
    })
   }

谢谢!在Swift 1.2中,我从响应的自动更正为NSHTTPURLResponse?变成了响应为!NSHTTPURLResponse? - Francis Jervis

3
如果您只想知道是否连接,并且正在使用SwiftUI:
服务:
import Foundation
import Network

class ConnectionService: ObservableObject {
    @Published var connected: Bool = false
    
    private var monitor: NWPathMonitor
    
    init() {
        monitor = NWPathMonitor()

        monitor.pathUpdateHandler = { [weak self] path in
            switch path.status {
            case .satisfied:
                self?.connected = true
            case .unsatisfied, .requiresConnection:
                self?.connected = false
            @unknown default:
                self?.connected = false
            }
        }

        monitor.start(queue: DispatchQueue.main)
    }
}

使用方法:

// step 1: add in your App struct
@StateObject var internet = ConnectionService() 

// step 2: add this modifier to your top level view
.environmentObject(internet) 

// step 3: add this in any child view
@EnvironmentObject var internet: ConnectionService 

// step 4: example usage in that child view
Text("internet: \(internet.connected ? "true" : "false")") 

2

Swift 4

if isInternetAvailable() {
    print("if called Internet Connectivity success \(isInternetAvailable())");
} else {
    print("else called Internet Connectivity success \(isInternetAvailable())");
}

func isInternetAvailable() -> Bool {
    var zeroAddress = sockaddr_in()
    zeroAddress.sin_len = UInt8(MemoryLayout.size(ofValue: zeroAddress))
    zeroAddress.sin_family = sa_family_t(AF_INET)
    let defaultRouteReachability = withUnsafePointer(to: &zeroAddress) {
        $0.withMemoryRebound(to: sockaddr.self, capacity: 1) {zeroSockAddress in
            SCNetworkReachabilityCreateWithAddress(nil, zeroSockAddress)
     }
    }

   var flags = SCNetworkReachabilityFlags()

   if !SCNetworkReachabilityGetFlags(defaultRouteReachability!, &flags) {
      return false
   }
   let isReachable = flags.contains(.reachable)
   let needsConnection = flags.contains(.connectionRequired)
   //   print(isReachable && !needsConnection)
   return (isReachable && !needsConnection)
}

2
如果您在项目中使用Alamofire,可以使用以下示例函数:

如果您在项目中使用Alamofire,可以使用以下示例函数:

import Alamofire

class Network {

    func isConnected(_ complition: @escaping(Bool) -> Void) {
        let retVal = NetworkReachabilityManager()!.isReachable
        complition(retVal)
    }

}

2

对于 Swift 5:

import Network
let monitor = NWPathMonitor()

func checkInterwebs() -> Bool {
    var status = false
    monitor.pathUpdateHandler = { path in
        if path.status == .satisfied {
            status = true  // online
        }
    }
    return status
}

似乎这不会起作用,因为您没有执行 monitor.start(queue: someQueue) - Tanin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接