如何在Swift中使用SCNetworkReachability
我试图将这个代码片段转换为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)
正如所料,我得到了一些其他的错误在自己的初始值使用的variables 。 我也了解这个错误的原因。 在C中,他们首先声明实例,然后填充参数。 就我所知,它在Swift中是不可能的。 所以我现在真的迷失在这个点上做什么。
我阅读了苹果在Swift中与C API进行交互的官方文档 ,但没有与结构一起工作的例子。
任何人都可以请帮我在这里? 我真的很感激。
谢谢。
更新:感谢马丁我能够通过最初的问题。 但斯威夫特仍然没有让我更容易。 我得到了多个新的错误。
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'在上面的行相同。
(这个答案由于Swift语言的变化而不断被延伸,这使得它有点令人困惑,现在我已经重写了它,并删除了所有涉及到Swift 1.x的东西。如果有人需要,可以在编辑历史中find旧代码它。)
这是你如何做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
:zeroAddress.sin_len = UInt8(sizeofValue(zeroAddress))
-
AF_INET
是一个Int32
,它必须被转换成sin_family
的正确types:zeroAddress.sin_family = sa_family_t(AF_INET)
-
withUnsafePointer(&zeroAddress) { ... }
将结构的地址传递给用作SCNetworkReachabilityCreateWithAddress()
参数的闭包。UnsafePointer($0)
转换是需要的,因为该函数需要一个指向sockaddr
而不是sockaddr_in
的指针。 -
从
withUnsafePointer()
返回的值是来自withUnsafePointer()
的返回值,它的types是SCNetworkReachability?
,即它是可选的。guard let
语句(Swift 2.0中的一个新特性)将unwrapped值赋给defaultRouteReachability
variables,如果它不nil
。 否则,执行else
块并返回函数。 - 从Swift 2开始,
SCNetworkReachabilityCreateWithAddress()
返回一个托pipe对象。 您不必明确地发布它。 -
从Swift 2开始,
SCNetworkReachabilityFlags
符合具有类似接口的OptionSetType
。 你创build一个空的标志variablesvar flags : SCNetworkReachabilityFlags = []
并检查标志
let isReachable = flags.contains(.Reachable) let needsConnection = flags.contains(.ConnectionRequired)
-
SCNetworkReachabilityGetFlags
的第二个参数的types为UnsafeMutablePointer<SCNetworkReachabilityFlags>
,这意味着你必须传递flagsvariables的地址 。
还要注意,从Swift 2开始,注册一个通知器callback是可能的,比较使用Swift和Swift 2的 C API – UnsafeMutablePointer <Void>来处理对象 。
Swift 3(Xcode 8)的更新:
不安全的指针不能简单地转换为不同types的指针(请参阅 – 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) }
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) } }) }
这与Swift无关,但最好的解决scheme是不使用Reachability来确定networking是否在线。 只要build立连接并处理失败的错误。 build立连接有时可以激活hibernate的离线无线电。
可达性的一个有效用途是在networking从脱机状态转换到在线时使用它来通知您。 此时您应该重试失败的连接。
最好的解决scheme是使用Swift 2
编写的ReachabilitySwift
类 ,并使用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()
像魅力一样工作。
请享用
更新了juanjo的答案来创build单例实例
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(){ }