Cómo usar SCNetworkReachability en Swift

Estoy intentando convertir este fragmento de código a Swift. Estoy luchando por despegar debido a algunas dificultades.

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

El primero y el principal problema que estoy teniendo es sobre cómo definir y trabajar con las estructuras C. En la primera línea ( struct sockaddr_in zeroAddress; ) del código anterior, creo que están definiendo una instancia llamada zeroAddress desde la estructura sockaddr_in (?), Supongo. Intenté declarar una var como esta.

 var zeroAddress = sockaddr_in() 

Pero obtengo el error Falta el argumento para el parámetro ‘sin_len’ en la llamada, que es comprensible porque esa estructura toma una serie de argumentos. Así que lo intenté de nuevo.

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

Como esperaba, obtengo otra Variable de error utilizada dentro de su propio valor inicial . También entiendo la causa de ese error. En C, declaran la instancia primero y luego llenan los parámetros. No es posible en Swift hasta donde yo sé. Así que estoy realmente perdido en este punto sobre qué hacer.

Leí el documento oficial de Apple sobre la interacción con las API de C en Swift, pero no tiene ejemplos al trabajar con estructuras.

¿Puede alguien ayudarme aquí? Realmente lo apreciaría.

Gracias.


ACTUALIZACIÓN: Gracias a Martin pude superar el problema inicial. Pero aún Swift no me lo está haciendo más fácil. Estoy recibiendo múltiples errores nuevos.

 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, UnsafePointer) // 'zeroAddress' is not a type var flags = SCNetworkReachabilityFlags() let didRetrieveFlags = SCNetworkReachabilityGetFlags(defaultRouteReachability, UnsafeMutablePointer) // '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 } 

EDIT 1: Bien, cambié esta línea a esto,

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

El nuevo error que recibo en esta línea es ‘UnsafePointer’ no es convertible a ‘CFAllocator’ . ¿Cómo pasa NULL en Swift?

También cambié esta línea y el error ya no está.

 let didRetrieveFlags = SCNetworkReachabilityGetFlags(defaultRouteReachability, &flags) 

EDIT 2: Pasé nil en esta línea después de ver esta pregunta. Pero esa respuesta contradice con la respuesta aquí . Dice que no hay equivalente a NULL en Swift.

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

De todos modos, recibo un nuevo error que dice ‘sockaddr_in’ no es idéntico a ‘sockaddr’ en la línea anterior.

(Esta respuesta se extendió repetidamente debido a cambios en el lenguaje Swift, lo que lo hizo un poco confuso. Ahora lo he reescrito y eliminado todo lo que se refiere a Swift 1.x. El código anterior se puede encontrar en el historial de edición si alguien necesita eso.)

Así es como lo harías en 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) } 

Explicaciones

  • A partir de Swift 1.2 (Xcode 6.3), las estructuras C importadas tienen un inicializador predeterminado en Swift, que inicializa todos los campos de la estructura a cero, por lo que la estructura de direcciones del socket se puede inicializar con

     var zeroAddress = sockaddr_in() 
  • sizeofValue() da el tamaño de esta estructura, esto tiene que convertirse a UInt8 para sin_len :

     zeroAddress.sin_len = UInt8(sizeofValue(zeroAddress)) 
  • AF_INET es un Int32 , esto tiene que ser convertido al tipo correcto para sin_family :

     zeroAddress.sin_family = sa_family_t(AF_INET) 
  • withUnsafePointer(&zeroAddress) { ... } pasa la dirección de la estructura al cierre donde se usa como argumento para SCNetworkReachabilityCreateWithAddress() . La UnsafePointer($0) es necesaria porque esa función espera un puntero a sockaddr , no a sockaddr_in .

  • El valor devuelto por withUnsafePointer() es el valor de retorno de SCNetworkReachabilityCreateWithAddress() y que tiene el tipo SCNetworkReachability? , es decir, es un opcional. La instrucción guard let (una nueva característica en Swift 2.0) asigna el valor defaultRouteReachability a la variable defaultRouteReachability si no es nil . De lo else , se ejecuta el bloque else y la función vuelve.

  • A partir de Swift 2, SCNetworkReachabilityCreateWithAddress() devuelve un objeto administrado. No tiene que liberarlo explícitamente.
  • A partir de Swift 2, SCNetworkReachabilityFlags cumple con OptionSetType que tiene una interfaz similar a un conjunto. Crea una variable de indicadores vacía con

     var flags : SCNetworkReachabilityFlags = [] 

    y busca banderas con

     let isReachable = flags.contains(.Reachable) let needsConnection = flags.contains(.ConnectionRequired) 
  • El segundo parámetro de SCNetworkReachabilityGetFlags tiene el tipo UnsafeMutablePointer , lo que significa que debe pasar la dirección de la variable de indicadores.

Tenga en cuenta también que el registro de una callback del notificador es posible a partir de Swift 2, compare Trabajar con API de C desde Swift y Swift 2 – UnsafeMutablePointer al objeto .


Actualización para Swift 3/4:

Los punteros inseguros no se pueden convertir simplemente en un puntero de otro tipo (ver – SE-0107 USAfeRawPointer API ). Aquí el código actualizado:

 import SystemConfiguration func connectedToNetwork() -> Bool { var zeroAddress = sockaddr_in() zeroAddress.sin_len = UInt8(MemoryLayout.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

Basado en la respuesta de 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.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.size) zeroAddress.sin_family = sa_family_t(AF_INET) return withUnsafePointer(to: &zeroAddress, { $0.withMemoryRebound(to: sockaddr.self, capacity: 1) { SCNetworkReachabilityCreateWithAddress(nil, $0) } }) } 

Esto no tiene nada que ver con Swift, pero la mejor solución es NO usar Accesibilidad para determinar si la red está en línea. Simplemente haga su conexión y maneje los errores si falla. Hacer una conexión a veces puede encender las radios inactivas fuera de línea.

El único uso válido de Accesibilidad es usarlo para notificarle cuando una red pasa de estar fuera de línea a estar en línea. En ese punto, debe volver a intentar las conexiones fallidas.

La mejor solución es usar la clase ReachabilitySwift , escrita en Swift 2 , y usa SCNetworkReachabilityRef .

Simple y fácil:

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

Trabajando como un encanto.

Disfrutar

actualizó la respuesta de juanjo para crear una instancia singleton

 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.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.size) zeroAddress.sin_family = sa_family_t(AF_INET) return withUnsafePointer(to: &zeroAddress, { $0.withMemoryRebound(to: sockaddr.self, capacity: 1) { SCNetworkReachabilityCreateWithAddress(nil, $0) } }) } } 

Uso

 if Reachability.shared.isConnectedToNetwork(){ }