【问题标题】:How to use SCNetworkReachability in Swift如何在 Swift 中使用 SCNetworkReachability
【发布时间】:2014-10-26 17:04:55
【问题描述】:

我正在尝试将this 代码 sn-p 转换为 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;) 中,我认为他们从 struct sockaddr_in(?) 定义了一个名为 zeroAddress 的实例。我尝试像这样声明var

var zeroAddress = sockaddr_in()

但我得到错误 Missing argument for parameter 'sin_len' in call 这是可以理解的,因为该结构需要许多参数。于是我又试了一次。

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

正如预期的那样,我得到了一些其他错误变量在其自己的初始值中使用。我也了解该错误的原因。在 C 中,它们首先声明实例,然后填充参数。据我所知,它在 Swift 中是不可能的。所以我现在真的不知道该怎么做。

我阅读了 Apple 的官方 document 关于在 Swift 中与 C API 交互的内容,但它没有使用结构的示例。

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

谢谢。


更新:感谢 Martin,我能够克服最初的问题。但是 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' is not convertible to 'CFAllocator'。如何在 Swift 中传递 NULL

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

let didRetrieveFlags = SCNetworkReachabilityGetFlags(defaultRouteReachability, &flags)

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

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

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

【问题讨论】:

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

标签: ios c struct swift reachability


【解决方案1】:

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

【讨论】:

  • 这需要更多的支持。这比 SystemConfiguration 框架更简洁、更友好。
【解决方案2】:

SwiftUI 采用 Mithra Sigam 的上述解决方案:

import Foundation
import Network

class NetworkReachabilityManager: ObservableObject {
    @Published var networkPathStatus: NWPath.Status
    @Published var availableInterfaces: [NWInterface]
    
    let monitor = NWPathMonitor()
    
    init() {
        monitor.start(queue: DispatchQueue.global(qos: .background))
        
        let currentPath = monitor.currentPath
        
        networkPathStatus = currentPath.status
        availableInterfaces = currentPath.availableInterfaces
        
        monitor.pathUpdateHandler = { [self] networkPath in
            networkPathStatus = networkPath.status
            availableInterfaces = networkPath.availableInterfaces
        }
    }
    
    deinit {
        monitor.cancel()
    }
}

【讨论】:

    【解决方案3】:

    这是在 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 屏幕

    【讨论】:

      【解决方案4】:

      (由于 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() 给出了这个结构的大小,它有 将sin_len转换为UInt8

        zeroAddress.sin_len = UInt8(sizeofValue(zeroAddress))
        
      • AF_INET 是一个Int32,必须将其转换为sin_family 的正确类型:

        zeroAddress.sin_family = sa_family_t(AF_INET)
        
      • withUnsafePointer(&amp;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 = []
        

        并使用

        检查标志
        let isReachable = flags.contains(.Reachable)
        let needsConnection = flags.contains(.ConnectionRequired)
        
      • SCNetworkReachabilityGetFlags的第二个参数有类型 UnsafeMutablePointer&lt;SCNetworkReachabilityFlags&gt;,这意味着你必须 传递标志变量的地址

      另请注意,注册通知回调是可能的 Swift 2,比较 Working with C APIs from SwiftSwift 2 - UnsafeMutablePointer<Void> to 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)
      }
      

      【讨论】:

      • @Isuru:UnsafePointer 是 C 指针的 Swift 等价物。 withUnsafePointer(&amp;zeroAddress)zeroAddress 的地址作为参数调用以下闭包{ ...}。在闭包内部,$0 代表该论点。 - 对不起,不可能用几句话来解释所有这些。查看 Swift 书中关于闭包的文档。 $0 是“速记参数名称”。
      • @JAL:你说得对,Apple 改变了“布尔值”映射到 Swift 的方式。感谢您的反馈,我会相应地更新答案。
      • 如果 wifi 未连接且 4G 已开启,但用户已指定该应用可能不使用蜂窝数据,则返回 true。任何解决方案?
      • @Jugale:你可以这样做:let cellular = flags.contains(.IsWWAN) 你可以返回一个touple而不是一个布尔值,比如:func connectedToNetwork() -&gt; (connected: Bool, cellular: Bool)
      • @Tejas:您可以使用任何 IP 地址而不是“零地址”,或者使用 SCNetworkReachabilityCreateWithName() 将主机名作为字符串。但请注意,SCNetworkReachability 仅检查发送到该地址的数据包是否可以离开本地设备。它不保证数据包会被主机真正接收到。
      【解决方案5】:

      更新了 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(){
      
      }
      

      【讨论】:

        【解决方案6】:

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

        【讨论】:

        • 为我工作也是 NET64/IPV6 的最佳方式,别忘了import SystemConfiguration
        • @juanjo,您如何使用您的代码设置您想要访问的主机
        【解决方案7】:

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

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

        【讨论】:

        【解决方案8】:

        最好的解决方案是使用ReachabilitySwiftclass,写成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()
        

        像魅力一样工作。

        享受

        【讨论】:

        • 我更喜欢接受的答案,因为它不需要集成任何第三方依赖项。此外,这并没有回答如何在 Swift 中使用 SCNetworkReachability 类的问题,它是一个用于检查有效网络连接的依赖项的建议。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2016-05-26
        • 2015-09-21
        • 2021-04-05
        • 2014-09-18
        • 2017-08-05
        • 2018-12-12
        相关资源
        最近更新 更多