如何在Swift中使用SCNetworkReachability

Isu*_*uru 97 c struct reachability ios swift

我正在尝试将此代码段转换为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;
}
Run Code Online (Sandbox Code Playgroud)

我遇到的第一个和主要问题是如何定义和使用C结构.在struct sockaddr_in zeroAddress;上面代码的第一行()中,我认为他们正在定义一个zeroAddress从struct sockaddr_in(?)调用的实例,我假设.我试着宣布var这样的话.

var zeroAddress = sockaddr_in()
Run Code Online (Sandbox Code Playgroud)

但是我在调用中得到参数'sin_len'的错误缺失参数,这是可以理解的,因为该结构需要许多参数.所以我又试了一次.

var zeroAddress = sockaddr_in(sin_len: sizeof(zeroAddress), sin_family: AF_INET, sin_port: nil, sin_addr: nil, sin_zero: nil)
Run Code Online (Sandbox Code Playgroud)

正如预期的那样,我在其自己的初始值中使用了一些其他错误变量.我也理解这个错误的原因.在C中,它们首先声明实例,然后填充参数.据我所知,它在Swift中是不可能的.所以我真的迷失了这一点.

我阅读了Apple 关于在Swift中与C API交互的官方文档,但它没有处理结构的例子.

有人可以帮帮我吗?我真的很感激.

谢谢.


更新:感谢Martin,我能够解决最初的问题.但斯威夫特仍然没有让我更容易.我收到了多个新错误.

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
}
Run Code Online (Sandbox Code Playgroud)

编辑1: 好的,我改变了这一行,

var defaultRouteReachability: SCNetworkReachabilityRef = SCNetworkReachabilityCreateWithAddress(UnsafePointer<Void>(), &zeroAddress)
Run Code Online (Sandbox Code Playgroud)

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

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

let didRetrieveFlags = SCNetworkReachabilityGetFlags(defaultRouteReachability, &flags)
Run Code Online (Sandbox Code Playgroud)

编辑2:nil看到这个问题后我通过了这一行.但是这个答案的答案矛盾在这里.它说NULL在Swift中没有相同的东西.

var defaultRouteReachability: SCNetworkReachabilityRef = SCNetworkReachabilityCreateWithAddress(nil, &zeroAddress)
Run Code Online (Sandbox Code Playgroud)

无论如何,我得到一个新的错误,说'sockaddr_in'与上面一行的'sockaddr'不同.

Mar*_*n R 232

(由于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)
}
Run Code Online (Sandbox Code Playgroud)

说明:

  • 从Swift 1.2(Xcode 6.3)开始,导入的C结构在Swift中有一个默认的初始化器,它将所有struct的字段初始化为零,因此套接字地址结构可以初始化为

    var zeroAddress = sockaddr_in()
    
    Run Code Online (Sandbox Code Playgroud)
  • sizeofValue()给出了这个结构的大小,这必须转换UInt8sin_len:

    zeroAddress.sin_len = UInt8(sizeofValue(zeroAddress))
    
    Run Code Online (Sandbox Code Playgroud)
  • AF_INETInt32,这必须转换为正确的类型sin_family:

    zeroAddress.sin_family = sa_family_t(AF_INET)
    
    Run Code Online (Sandbox Code Playgroud)
  • withUnsafePointer(&zeroAddress) { ... }将结构的地址传递给闭包,在闭包中将其用作参数 SCNetworkReachabilityCreateWithAddress().该UnsafePointer($0) 转换是必要的,因为该功能需要一个指针 sockaddr,而不是sockaddr_in.

  • 从返回的值withUnsafePointer()是从返回值SCNetworkReachabilityCreateWithAddress()和具有类型SCNetworkReachability?,即它是一个可选的.该guard let语句(Swift 2.0中的一个新功能)将未包装的值分配给defaultRouteReachability变量(如果不是)nil.否则else执行该块并返回该函数.

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

    var flags : SCNetworkReachabilityFlags = []
    
    Run Code Online (Sandbox Code Playgroud)

    并检查标志

    let isReachable = flags.contains(.Reachable)
    let needsConnection = flags.contains(.ConnectionRequired)
    
    Run Code Online (Sandbox Code Playgroud)
  • 第二个参数SCNetworkReachabilityGetFlags有类型 UnsafeMutablePointer<SCNetworkReachabilityFlags>,这意味着你必须传递flags变量的地址.

另请注意,从Swift 2开始,可以注册通知程序回调,比较使用SwiftSwift 2中的C API - UnsafeMutablePointer <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)
}
Run Code Online (Sandbox Code Playgroud)

  • @Jugale:你可以这样做:`let cellular = flags.contains(.IsWWAN)`你可以返回一个touple而不是一个布尔值,例如:`func connectedToNetwork() - >(connected:Bool,cellular:Bool) (5认同)
  • @Isuru:UnsafePointer是Swift的C指针.`withUnsafePointer(&zeroAddress)`调用以下闭包`{...}`,地址为`zeroAddress`作为参数.在闭包内部,`$ 0`代表该论点. - 抱歉,用几句话解释所有这些都是不可能的.看一下Swift书中有关闭包的文档.$ 0是"速记参数名称". (4认同)
  • @Tejas:您可以使用任何IP地址而不是"零地址",或使用主机名为SCNetworkReachabilityCreateWithName()作为字符串.但请注意,SCNetworkReachability仅检查发送到该地址的数据包是否可以离开本地设备.它不保证主机实际上会接收数据包. (3认同)

Mit*_*gam 15

斯威夫特 5,使用NWPathMonitor

import Network

func configureNetworkMonitor(){
        let monitor = NWPathMonitor()
        
        monitor.pathUpdateHandler = { path in
            
            if path.status != .satisfied {
                print("not connected")
            }
            else if path.usesInterfaceType(.cellular) {
                print("Cellular")
            }
            else if path.usesInterfaceType(.wifi) {
                print("WIFI")
            }
            else if path.usesInterfaceType(.wiredEthernet) {
                print("Ethernet")
            }
            else if path.usesInterfaceType(.other){
                print("Other")
            }else if path.usesInterfaceType(.loopback){
                print("Loop Back")
            }
        }
        
        monitor.start(queue: DispatchQueue.global(qos: .background))
    }
Run Code Online (Sandbox Code Playgroud)

  • 这需要更多的赞成票。这比 SystemConfiguration 框架更干净、更用户友好。 (3认同)

jua*_*njo 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)
        }
    })
}
Run Code Online (Sandbox Code Playgroud)

  • 为我工作也是NET64/IPV6的最佳方式,不要忘记`import SystemConfiguration` (2认同)

Eri*_*icS 6

这与Swift无关,但最好的解决方案是不使用Reachability来确定网络是否在线.如果失败,只需建立连接并处理错误.建立连接有时可以启动休眠的离线无线电.

可达性的一个有效用途是使用它来在网络从离线转换到在线时通知您.此时,您应该重试失败的连接.

  • 从历史上看,如果收音机断电,则可达性不起作用.我还没有在iOS 9中的现代设备上对此进行过测试,但我保证它只会在早期版本的iOS中导致上传失败,只需简单地建立连接即可.如果您希望上传仅通过WiFi,您应该使用`NSURLSession` API和`NSURLSessionConfiguration.allowsCellularAccess = false`. (2认同)