如何在 Swift 中使用 SCNetworkReacability

我试图转换 这个代码片段到斯威夫特。我挣扎着离开地面由于一些困难。

- (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 中与 C API 交互的 文件,但它没有使用 structs 的例子。

有人能帮帮我吗? 我会很感激的。

谢谢你。


更新: 多亏了马丁,我才能解决最初的问题。但斯威夫特还是没让我好过。我收到了很多新的错误。

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’。如何在 Swift 中传递 NULL

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

let didRetrieveFlags = SCNetworkReachabilityGetFlags(defaultRouteReachability, &flags)

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

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

无论如何,我得到一个新的错误说 ‘ sockaddr _ in’与‘ sockaddr’不同在上面的行。

46300 次浏览

(This answer was extended repeatedly due to changes in the Swift language, which made it a bit confusing. I have now rewritten it and removed everything which refers to Swift 1.x. The older code can be found in the edit history if somebody needs it.)

This is how you would do it in 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)
}

Explanations:

  • As of Swift 1.2 (Xcode 6.3), imported C structs have a default initializer in Swift, which initializes all of the struct's fields to zero, so the socket address structure can be initialized with

    var zeroAddress = sockaddr_in()
    
  • sizeofValue() gives the size of this structure, this has to be converted to UInt8 for sin_len:

    zeroAddress.sin_len = UInt8(sizeofValue(zeroAddress))
    
  • AF_INET is an Int32, this has to be converted to the correct type for sin_family:

    zeroAddress.sin_family = sa_family_t(AF_INET)
    
  • withUnsafePointer(&zeroAddress) { ... } passes the address of the structure to the closure where it is used as argument for SCNetworkReachabilityCreateWithAddress(). The UnsafePointer($0) conversion is needed because that function expects a pointer to sockaddr, not sockaddr_in.

  • The value returned from withUnsafePointer() is the return value from SCNetworkReachabilityCreateWithAddress() and that has the type SCNetworkReachability?, i.e. it is an optional. The guard let statement (a new feature in Swift 2.0) assigns the unwrapped value to the defaultRouteReachability variable if it is not nil. Otherwise the else block is executed and the function returns.

  • As of Swift 2, SCNetworkReachabilityCreateWithAddress() returns a managed object. You don't have to release it explicitly.
  • As of Swift 2, SCNetworkReachabilityFlags conforms to OptionSetType which has a set-like interface. You create an empty flags variable with

    var flags : SCNetworkReachabilityFlags = []
    

    and check for flags with

    let isReachable = flags.contains(.Reachable)
    let needsConnection = flags.contains(.ConnectionRequired)
    
  • The second parameter of SCNetworkReachabilityGetFlags has the type UnsafeMutablePointer<SCNetworkReachabilityFlags>, which means that you have to pass the address of the flags variable.

Note also that registering a notifier callback is possible as of Swift 2, compare Working with C APIs from Swift and Swift 2 - UnsafeMutablePointer<Void> to object.


Update for Swift 3/4:

Unsafe pointers cannot be simply be converted to a pointer of a different type anymore (see - SE-0107 UnsafeRawPointer API). Here the updated code:

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

The best solution is to use ReachabilitySwift class, written in Swift 2, and uses SCNetworkReachabilityRef.

Simple and easy:

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

Working like a charm.

Enjoy

This has nothing to do with Swift, but the best solution is to NOT use Reachability to determine whether the network is online. Just make your connection and handle errors if it fails. Making a connection can at times fire up the dormant offline radios.

The one valid use of Reachability is to use it to notify you when a network transitions from offline to online. At that point you should retry failed connections.

Swift 3, IPv4, IPv6

Based on the Martin R's answer:

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

updated juanjo's answer to create singleton instance

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

Usage

if Reachability.shared.isConnectedToNetwork(){


}

This is in Swift 4.0

I am using this framework https://github.com/ashleymills/Reachability.swift
And Install Pod ..
In 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")
}
}
}

The reachabilityViewController screen will appear if internet is not there

Swift 5, Using 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))
}

A SwiftUI take on Mithra Sigam's solution above:

import SwiftUI
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
DispatchQueue.main.async {
networkPathStatus = networkPath.status
availableInterfaces = networkPath.availableInterfaces
}
}
}
    

deinit {
monitor.cancel()
}
}