Cómo utilizar SCNetworkReachability en Swift

99

Estoy intentando convertir este fragmento de código a Swift. Estoy luchando para 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 primer y principal problema que tengo es cómo definir y trabajar con estructuras C. En la primera línea ( struct sockaddr_in zeroAddress;) del código anterior, creo que están definiendo una instancia llamada zeroAddressdesde la estructura sockaddr_in (?), Supongo. Intenté declarar algo varasí.

var zeroAddress = sockaddr_in()

Pero obtengo el error Faltante argumento para el parámetro 'sin_len' en la llamada, lo cual es comprensible porque esa estructura toma varios 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, primero declaran la instancia y luego completan los parámetros. No es posible en Swift que yo sepa. 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 sobre cómo trabajar con estructuras.

¿Alguien puede ayudarme aquí? Realmente lo agradecería.

Gracias.


ACTUALIZACIÓN: Gracias a Martin pude superar el problema inicial. Pero aún así Swift no me lo pone más fácil. Recibo varios 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<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
}

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

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

El nuevo error que recibo en esta línea es 'UnsafePointer' no se puede convertir a 'CFAllocator' . ¿Cómo pasar NULLen Swift?

También cambié esta línea y el error desapareció ahora.

let didRetrieveFlags = SCNetworkReachabilityGetFlags(defaultRouteReachability, &flags)

EDICIÓN 2: Pasé nilen esta línea después de ver esta pregunta. Pero esa respuesta contradice la respuesta aquí . Dice que no hay equivalente NULLen Swift.

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

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

Isuru
fuente
estoy teniendo un error en la línea if! SCNetworkReachabilityGetFlags (defaultRouteReachability, & flags) es decir, operador unario! no se puede aplicar a un operando de tipo booleano. . . . por favor ayuda.
Zeebok

Respuestas:

236

(Esta respuesta se extendió repetidamente debido a cambios en el lenguaje Swift, lo que lo hizo un poco confuso. Ahora lo reescribí y eliminé 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ía 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 la dirección del socket se puede inicializar con

    var zeroAddress = sockaddr_in()
  • sizeofValue()da el tamaño de esta estructura, esto debe convertirse a UInt8para sin_len:

    zeroAddress.sin_len = UInt8(sizeofValue(zeroAddress))
  • AF_INETes un Int32, debe convertirse 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) conversión es necesaria porque esa función espera un puntero sockaddr, no sockaddr_in.

  • El valor devuelto de withUnsafePointer()es el valor de retorno de SCNetworkReachabilityCreateWithAddress()y que tiene el tipo SCNetworkReachability?, es decir, es opcional. La guard letdeclaración (una nueva característica en Swift 2.0) asigna el valor sin envolver a la defaultRouteReachabilityvariable si no es así nil. De lo contrario, el elsebloque se ejecuta y la función vuelve.

  • A partir de Swift 2, SCNetworkReachabilityCreateWithAddress()devuelve un objeto administrado. No tiene que publicarlo explícitamente.
  • A partir de Swift 2, se SCNetworkReachabilityFlagsajusta a lo OptionSetTypeque tiene una interfaz tipo set. Creas una variable de banderas vacía con

    var flags : SCNetworkReachabilityFlags = []

    y busque banderas con

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

Tenga en cuenta también que es posible registrar una devolución de llamada de notificador a partir de Swift 2, compare Trabajar con las API de C de Swift y Swift 2 - UnsafeMutablePointer <Void> al objeto .


Actualización para Swift 3/4:

Los punteros inseguros ya no se pueden convertir simplemente en un puntero de un tipo diferente (consulte - SE-0107 API UnsafeRawPointer ). Aquí el código actualizado:

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)
}
Martín R
fuente
4
@Isuru: UnsafePointer es el equivalente Swift de un puntero C. withUnsafePointer(&zeroAddress)llama al siguiente cierre { ...}con la dirección de zeroAddresscomo argumento. Dentro del cierre, $0defiende ese argumento. - Lo siento, es imposible explicar todo eso en unas pocas frases. Eche un vistazo a la documentación sobre cierres en el libro Swift. $ 0 es un "nombre de argumento abreviado".
Martin R
1
@JAL: Tienes razón, Apple cambió la forma en que se asigna un "booleano" a Swift. Gracias por sus comentarios, actualizaré la respuesta en consecuencia.
Martin R
1
Esto regresa truesi el wifi no está conectado y 4G está encendido pero el usuario ha especificado que la aplicación no puede usar datos móviles. ¿Alguna solución?
Max Chuquimia
5
@Jugale: Podrías hacer algo como: let cellular = flags.contains(.IsWWAN) Puedes devolver un touple en lugar de un booleano, como: func connectedToNetwork() -> (connected: Bool, cellular: Bool)
EdFunke
3
@Tejas: Puede usar cualquier dirección IP en lugar de la "dirección cero", o usar SCNetworkReachabilityCreateWithName () con un nombre de host como cadena. Pero tenga en cuenta que SCNetworkReachability solo verifica que un paquete enviado a esa dirección pueda salir del dispositivo local. No garantiza que el host realmente reciba el paquete de datos.
Martin R
12

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<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)
        }
    })
}
juanjo
fuente
2
Trabajando para mí también es la mejor manera para NET64 / IPV6 también, no olvidesimport SystemConfiguration
Bhavin_m
@juanjo, ¿cómo se configura un host al que desea llegar usando su código?
user2924482
6

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 fuera de línea inactivas.

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

EricS
fuente
Todavía con errores. Simplemente haga la conexión y maneje los errores. Vea openradar.me/21581686 y mail-archive.com/[email protected]/msg00200.html y el primer comentario aquí mikeash.com/pyblog/friday-qa-2013-06-14-reachability.html
EricS
No entiendo esto, ¿no le gustaría saber si estaba en WiFi o 3G antes de intentar una gran carga?
dumbledad
3
Históricamente, la accesibilidad no funcionaba si las radios estaban apagadas. No he probado esto en dispositivos modernos en iOS 9, pero garantizo que solía causar fallas de carga en versiones anteriores de iOS cuando simplemente hacer una conexión habría funcionado bien. Si desea que una carga solo se realice a través de WiFi, debe usar la NSURLSessionAPI con NSURLSessionConfiguration.allowsCellularAccess = false.
EricS
3

La mejor solución es usar ReachabilitySwift class , escrito Swift 2y uses 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

Bonnke
fuente
7
Prefiero la respuesta aceptada ya que no requiere la integración de dependencias de terceros. Además, esto no responde a la pregunta de cómo usar la SCNetworkReachabilityclase en Swift, es una sugerencia de una dependencia para usar para verificar una conexión de red válida.
JAL
1

actualizó la respuesta de juanjo para crear una instancia de 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<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)
            }
        })
    }
}

Uso

if Reachability.shared.isConnectedToNetwork(){

}
anoop4real
fuente
1

Esto es en Swift 4.0

Estoy usando este marco https://github.com/ashleymills/Reachability.swift
e instalo Pod ..
En 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")
    }
}
}

La pantalla reachabilityViewController aparecerá si no hay Internet

Sreekanth G
fuente