如何在Swift中使用SCNetworkReachability

时间:2014-09-02 12:17:35

标签: ios c struct swift reachability

我试图将this代码段转换为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()

但我收到错误缺少参数' sin_len'在调用这是可以理解的,因为该结构需要许多参数。所以我再试一次。

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)

我在这一行得到的新错误是&#39; UnsafePointer&#39;不能转换为&#39; CFAllocator&#39; 。如何在Swift中传递NULL

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

let didRetrieveFlags = SCNetworkReachabilityGetFlags(defaultRouteReachability, &flags)

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

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

无论如何,我收到一条新错误,上面写着&#39; sockaddr_in&#39;与上面一行的&#39; sockaddr&#39; 不同。

8 个答案:

答案 0 :(得分:230)

(由于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中有一个默认的初始化器,它将所有struct的字段初始化为零,因此套接字地址结构可以用

    var zeroAddress = sockaddr_in()
    
  • sizeofValue()给出了这个结构的大小 要转换为UInt8 sin_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语句(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<SCNetworkReachabilityFlags>,这意味着你必须这样做 传递flags变量的地址

另请注意,注册通知程序回调是可能的 Swift 2,比较Working with C APIs from SwiftSwift 2 - UnsafeMutablePointer<Void> to object


Swift 3/4的更新:

不安全的指针不能简单地转换为a的指针 不同类型了(见 - 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)
}

答案 1 :(得分:11)

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 :(得分:6)

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

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

答案 3 :(得分:4)

最佳解决方案是使用ReachabilitySwift中编写的Swift 2 class,并使用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()

像魅力一样工作。

享受

答案 4 :(得分: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(){

}

答案 5 :(得分:0)

这是在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")
    }
}
}

如果没有互联网,则会出现屏幕

答案 6 :(得分:0)

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

答案 7 :(得分:0)

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