如何在Swift中使用SCNetworkReachability


99

我正在尝试将此代码段转换为Swift。由于某些困难,我正在努力下车。

- (BOOL) connectedToNetwork
{
    // Create zero addy
    struct sockaddr_in zeroAddress;
    bzero(&zeroAddress, sizeof(zeroAddress));
    zeroAddress.sin_len = sizeof(zeroAddress);
    zeroAddress.sin_family = AF_INET;

    // Recover reachability flags
    SCNetworkReachabilityRef defaultRouteReachability = SCNetworkReachabilityCreateWithAddress(NULL, (struct sockaddr *)&zeroAddress);
    SCNetworkReachabilityFlags flags;

    BOOL didRetrieveFlags = SCNetworkReachabilityGetFlags(defaultRouteReachability, &flags);
    CFRelease(defaultRouteReachability);

    if (!didRetrieveFlags)
    {
        return NO;
    }

    BOOL isReachable = flags & kSCNetworkFlagsReachable;
    BOOL needsConnection = flags & kSCNetworkFlagsConnectionRequired;

    return (isReachable && !needsConnection) ? YES : NO;
}

我遇到的第一个也是主要的问题是如何定义和使用C结构。struct sockaddr_in zeroAddress;我认为,在上述代码的第一行()中,我认为他们正在定义zeroAddress从struct sockaddr_in(?)调用的实例。我试图声明var这样的。

var zeroAddress = sockaddr_in()

但是我在调用中得到了参数'sin_len'的错误Missing参数,这是可以理解的,因为该结构需要多个参数。所以我再次尝试。

var zeroAddress = sockaddr_in(sin_len: sizeof(zeroAddress), sin_family: AF_INET, sin_port: nil, sin_addr: nil, sin_zero: nil)

不出所料,我在它自己的初始值中使用了其他错误变量。我也了解该错误的原因。在C语言中,他们首先声明实例,然后填充参数。据我所知,这在Swift中是不可能的。因此,我现在真的迷失了该做什么。

我阅读了Apple的有关在Swift中与C API进行交互的官方文档,但其中没有使用结构的示例。

有人可以在这里帮我吗?我真的很感激。

谢谢。


更新:感谢马丁,我得以解决最初的问题。但是Swift仍然不能让我更容易。我遇到了多个新错误。

func connectedToNetwork() -> 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)

    var defaultRouteReachability: SCNetworkReachabilityRef = SCNetworkReachabilityCreateWithAddress(UnsafePointer<Void>, UnsafePointer<zeroAddress>) // 'zeroAddress' is not a type
    var flags = SCNetworkReachabilityFlags()

    let didRetrieveFlags = SCNetworkReachabilityGetFlags(defaultRouteReachability, UnsafeMutablePointer<flags>) // 'flags' is not a type
    defaultRouteReachability.dealloc(1) // 'SCNetworkReachabilityRef' does not have a member named 'dealloc'

    if didRetrieveFlags == false {
        return false
    }

    let isReachable: Bool = flags & kSCNetworkFlagsReachable // Cannot invoke '&' with an argument list of type '(@lvalue UInt32, Int)'
    let needsConnection: Bool = flags & kSCNetworkFlagsConnectionRequired // Cannot invoke '&' with an argument list of type '(@lvalue UInt32, Int)'

    return (isReachable && !needsConnection) ? true : false
}

编辑1: 好的,我将此行更改为此,

var defaultRouteReachability: SCNetworkReachabilityRef = SCNetworkReachabilityCreateWithAddress(UnsafePointer<Void>(), &zeroAddress)

我在这一行遇到的新错误是'UnsafePointer'无法转换为'CFAllocator'。如何通过NULLSwift?

我也改变了这一行,错误现在消失了。

let didRetrieveFlags = SCNetworkReachabilityGetFlags(defaultRouteReachability, &flags)

编辑2:nil看到问题后,我通过了这一行。但是那个答案与这里的答案相矛盾。它说NULL在Swift中没有等效的功能。

var defaultRouteReachability: SCNetworkReachabilityRef = SCNetworkReachabilityCreateWithAddress(nil, &zeroAddress)

无论如何,我收到一个新错误,说上一行中的'sockaddr_in'与'sockaddr'不相同


如果!SCNetworkReachabilityGetFlags(defaultRouteReachability,&flags)即一元运算符,我在行上会出错!不能应用于布尔类型的操作数。。。。请帮忙。
Zeebok '16

Answers:


236

(由于Swift语言的更改,此答案反复扩展,这使它有点混乱。我现在将其重写并删除了所有与Swift 1.x相关的内容。如果有人需要,可以在编辑历史记录中找到较旧的代码。它。)

这就是您在Swift 2.0(Xcode 7)中的做法:

import SystemConfiguration

func connectedToNetwork() -> Bool {

    var zeroAddress = sockaddr_in()
    zeroAddress.sin_len = UInt8(sizeofValue(zeroAddress))
    zeroAddress.sin_family = sa_family_t(AF_INET)

    guard let defaultRouteReachability = withUnsafePointer(&zeroAddress, {
        SCNetworkReachabilityCreateWithAddress(nil, UnsafePointer($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)
}

说明:

  • 从Swift 1.2(Xcode 6.3)开始,导入的C结构在Swift中具有默认的初始化程序,该初始化程序将该结构的所有字段初始化为零,因此可以使用以下方式初始化套接字地址结构:

    var zeroAddress = sockaddr_in()
  • sizeofValue()给出此结构的大小,必须将其转换UInt8sin_len

    zeroAddress.sin_len = UInt8(sizeofValue(zeroAddress))
  • AF_INETInt32,必须将其转换为以下类型的正确类型sin_family

    zeroAddress.sin_family = sa_family_t(AF_INET)
  • withUnsafePointer(&zeroAddress) { ... }将结构的地址传递到闭包,在闭包处作为实参 SCNetworkReachabilityCreateWithAddress()。该UnsafePointer($0) 转换是必要的,因为该功能需要一个指针 sockaddr,而不是sockaddr_in

  • 从中withUnsafePointer()返回的值是从中返回的值SCNetworkReachabilityCreateWithAddress(),其类型为SCNetworkReachability?,即它是可选的。的guard let声明(在夫特2.0新功能)分配展开值到defaultRouteReachability如果不是可变nil。否则else执行块,然后函数返回。

  • 从Swift 2开始,SCNetworkReachabilityCreateWithAddress()返回一个托管对象。您不必显式释放它。
  • 从Swift 2开始,SCNetworkReachabilityFlags符合的 OptionSetType具有类似set的界面。您使用创建一个空标志变量

    var flags : SCNetworkReachabilityFlags = []

    并检查标志

    let isReachable = flags.contains(.Reachable)
    let needsConnection = flags.contains(.ConnectionRequired)
  • 的第二个参数SCNetworkReachabilityGetFlags具有type UnsafeMutablePointer<SCNetworkReachabilityFlags>,这意味着您必须传递flags变量的地址

另请注意,从Swift 2开始,可以注册通知程序回调,将使用SwiftSwift 2中的C API与UnobjectedMutablePointer <Void>与object进行比较


Swift 3/4的更新:

不安全的指针不能再简单地转换为其他类型的指针(请参见-SE-0107 UnsafeRawPointer API)。这里是更新的代码:

import SystemConfiguration

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

4
@Isuru:UnsafePointer是Swift的C指针。使用as参数的地址withUnsafePointer(&zeroAddress)调用以下闭包。在闭包内部,代表该论点。-对不起,不可能用几句话来解释所有这些。看看Swift书中有关闭包的文档。$ 0是“简写参数名称”。{ ...}zeroAddress$0
马丁R

1
@JAL:是的,Apple更改了将“布尔”映射到Swift的方式。感谢您的反馈,我将相应地更新答案。
Martin R

1
这将返回true如果无线网络没有连接和4G是对的,但用户指定的应用程序可能无法使用蜂窝数据。任何解决方案?
Max Chuquimia 2015年

5
@Jugale:您可以执行以下操作: let cellular = flags.contains(.IsWWAN) 您可以返回touple而不是布尔值,例如: func connectedToNetwork() -> (connected: Bool, cellular: Bool)
EdFunke 2015年

3
@Tejas:可以使用任何IP地址代替“零地址”,也可以将SCNetworkReachabilityCreateWithName()与主机名一起用作字符串。但是请注意,SCNetworkReachability仅检查发送到该地址的数据包是否可以离开本地设备。它不能保证主机实际会收到数据包。
Martin R

12

Swift 3,IPv4,IPv6

根据Martin R的答案:

import SystemConfiguration

func isConnectedToNetwork() -> Bool {
    guard let flags = getFlags() else { return false }
    let isReachable = flags.contains(.reachable)
    let needsConnection = flags.contains(.connectionRequired)
    return (isReachable && !needsConnection)
}

func getFlags() -> SCNetworkReachabilityFlags? {
    guard let reachability = ipv4Reachability() ?? ipv6Reachability() else {
        return nil
    }
    var flags = SCNetworkReachabilityFlags()
    if !SCNetworkReachabilityGetFlags(reachability, &flags) {
        return nil
    }
    return flags
}

func ipv6Reachability() -> SCNetworkReachability? {
    var zeroAddress = sockaddr_in6()
    zeroAddress.sin6_len = UInt8(MemoryLayout<sockaddr_in>.size)
    zeroAddress.sin6_family = sa_family_t(AF_INET6)

    return withUnsafePointer(to: &zeroAddress, {
        $0.withMemoryRebound(to: sockaddr.self, capacity: 1) {
            SCNetworkReachabilityCreateWithAddress(nil, $0)
        }
    })
}

func ipv4Reachability() -> SCNetworkReachability? {
    var zeroAddress = sockaddr_in()
    zeroAddress.sin_len = UInt8(MemoryLayout<sockaddr_in>.size)
    zeroAddress.sin_family = sa_family_t(AF_INET)

    return withUnsafePointer(to: &zeroAddress, {
        $0.withMemoryRebound(to: sockaddr.self, capacity: 1) {
            SCNetworkReachabilityCreateWithAddress(nil, $0)
        }
    })
}

2
为我工作也是NET64 / IPV6的最佳方式,别忘了import SystemConfiguration
Bhavin_m 2016年

@juanjo,您如何使用代码设置要访问的主机
user2924482

6

这与Swift无关,但是最好的解决方案是不要使用可达性来确定网络是否在线。只需建立连接并处理失败即可的错误。建立连接有时会触发休眠的离线无线电。

网络可达性从脱机过渡到联机时,可到达性的一种有效用法是使用它来通知您。此时,您应该重试失败的连接。



仍然越野车。只需建立连接并处理错误即可。参见openradar.me/21581686mail-archive.com/macnetworkprog@lists.apple.com/msg00200.html以及此处的第一条评论mikeash.com/pyblog/friday-qa-2013-06-14-reachability.html
EricS

我不明白这一点-您是否想在尝试大上传之前不知道您是使用WiFi还是3G?
dumbledad '16

3
从历史上看,如果收音机掉电了,可达性就不起作用。我尚未在iOS 9的现代设备上对此进行过测试,但我保证,如果仅进行连接就可以在早期版本的iOS中导致上传失败。如果您只想通过WiFi上传图片,则应将NSURLSessionAPI与结合使用NSURLSessionConfiguration.allowsCellularAccess = false
EricS '16

3

最好的解决方案是使用ReachabilitySwift ,编写Swift 2和使用SCNetworkReachabilityRef

简单易行:

let reachability = Reachability.reachabilityForInternetConnection()

reachability?.whenReachable = { reachability in
    // keep in mind this is called on a background thread
    // and if you are updating the UI it needs to happen
    // on the main thread, like this:
    dispatch_async(dispatch_get_main_queue()) {
        if reachability.isReachableViaWiFi() {
            print("Reachable via WiFi")
        } else {
            print("Reachable via Cellular")
        }
    }
}
reachability?.whenUnreachable = { reachability in
    // keep in mind this is called on a background thread
    // and if you are updating the UI it needs to happen
    // on the main thread, like this:
    dispatch_async(dispatch_get_main_queue()) {
        print("Not reachable")
    }
}

reachability?.startNotifier()

像魅力一样工作。

请享用


7
我更喜欢接受的答案,因为它不需要集成任何第三方依赖项。另外,这没有回答如何SCNetworkReachability在Swift中使用类的问题,它建议使用依赖项来检查有效的网络连接。
2015年

1

更新了juanjo的答案以创建单例实例

import Foundation
import SystemConfiguration

final class Reachability {

    private init () {}
    class var shared: Reachability {
        struct Static {
            static let instance: Reachability = Reachability()
        }
        return Static.instance
    }

    func isConnectedToNetwork() -> Bool {
        guard let flags = getFlags() else { return false }
        let isReachable = flags.contains(.reachable)
        let needsConnection = flags.contains(.connectionRequired)
        return (isReachable && !needsConnection)
    }

    private func getFlags() -> SCNetworkReachabilityFlags? {
        guard let reachability = ipv4Reachability() ?? ipv6Reachability() else {
            return nil
        }
        var flags = SCNetworkReachabilityFlags()
        if !SCNetworkReachabilityGetFlags(reachability, &flags) {
            return nil
        }
        return flags
    }

    private func ipv6Reachability() -> SCNetworkReachability? {
        var zeroAddress = sockaddr_in6()
        zeroAddress.sin6_len = UInt8(MemoryLayout<sockaddr_in>.size)
        zeroAddress.sin6_family = sa_family_t(AF_INET6)

        return withUnsafePointer(to: &zeroAddress, {
            $0.withMemoryRebound(to: sockaddr.self, capacity: 1) {
                SCNetworkReachabilityCreateWithAddress(nil, $0)
            }
        })
    }
    private func ipv4Reachability() -> SCNetworkReachability? {
        var zeroAddress = sockaddr_in()
        zeroAddress.sin_len = UInt8(MemoryLayout<sockaddr_in>.size)
        zeroAddress.sin_family = sa_family_t(AF_INET)

        return withUnsafePointer(to: &zeroAddress, {
            $0.withMemoryRebound(to: sockaddr.self, capacity: 1) {
                SCNetworkReachabilityCreateWithAddress(nil, $0)
            }
        })
    }
}

用法

if Reachability.shared.isConnectedToNetwork(){

}

1

这是在Swift 4.0中

我正在使用此框架https://github.com/ashleymills/Reachability.swift
并安装Pod ..
AppDelegate中

var window: UIWindow?
var reachability = InternetReachability()!
var reachabilityViewController : UIViewController? = nil

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    // Override point for customization after application launch.

    reachabilityChecking()
    return true
}

extension AppDelegate {

func reachabilityChecking() {    
    reachability.whenReachable = { reachability in
        DispatchQueue.main.async {
            print("Internet is OK!")
            if reachability.connection != .none && self.reachabilityViewController != nil {

            }
        }
    }
    reachability.whenUnreachable = { _ in
        DispatchQueue.main.async {
            print("Internet connection FAILED!")
            let storyboard = UIStoryboard(name: "Reachability", bundle: Bundle.main)
            self.reachabilityViewController = storyboard.instantiateViewController(withIdentifier: "ReachabilityViewController")
            let rootVC = self.window?.rootViewController
            rootVC?.present(self.reachabilityViewController!, animated: true, completion: nil)
        }
    }
    do {
        try reachability.startNotifier()
    } catch {
        print("Could not start notifier")
    }
}
}

如果不存在互联网,将会出现reachabilityViewController屏幕

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.