Come utilizzare SCNetworkReachability in Swift


99

Sto cercando di convertire questo frammento di codice in Swift. Faccio fatica a decollare a causa di alcune difficoltà.

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

Il primo e il problema principale che sto avendo è su come definire e lavorare con le strutture C. Nella prima riga ( struct sockaddr_in zeroAddress;) del codice sopra, penso che stiano definendo un'istanza chiamata zeroAddressdalla struttura sockaddr_in (?), Presumo. Ho provato a dichiarare un varcome questo.

var zeroAddress = sockaddr_in()

Ma ottengo l'errore Argomento mancante per il parametro "sin_len" nella chiamata, il che è comprensibile perché quella struttura accetta un numero di argomenti. Quindi ho provato di nuovo.

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

Come previsto, ottengo qualche altra variabile di errore utilizzata all'interno del proprio valore iniziale . Capisco anche la causa di quell'errore. In C, dichiarano prima l'istanza e poi riempiono i parametri. Non è possibile in Swift per quanto ne so. Quindi a questo punto sono davvero perso su cosa fare.

Ho letto il documento ufficiale di Apple sull'interazione con le API C in Swift ma non ha esempi di lavoro con le strutture.

Qualcuno può aiutarmi qui? Lo apprezzerei davvero.

Grazie.


AGGIORNAMENTO: Grazie a Martin sono riuscito a superare il problema iniziale. Ma ancora Swift non mi sta rendendo le cose più facili. Ricevo più nuovi errori.

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
}

EDIT 1: Ok, ho cambiato questa riga in questa,

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

Il nuovo errore che ricevo su questa riga è che "UnsafePointer" non è convertibile in "CFAllocator" . Come passi NULLin Swift?

Inoltre ho cambiato questa riga e l'errore ora è sparito.

let didRetrieveFlags = SCNetworkReachabilityGetFlags(defaultRouteReachability, &flags)

EDIT 2: sono passato nilin questa riga dopo aver visto questa domanda. Ma quella risposta è in contraddizione con la risposta qui . Dice che non esiste un equivalente NULLin Swift.

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

Comunque ricevo un nuovo errore che dice che "sockaddr_in" non è identico a "sockaddr" nella riga sopra.


Sto riscontrando un errore alla riga se! SCNetworkReachabilityGetFlags (defaultRouteReachability, & flags) cioè operatore unario! non può essere applicato a un operando di tipo Boolean. . . . per favore aiuto.
Zeebok

Risposte:


236

(Questa risposta è stata estesa ripetutamente a causa di modifiche nella lingua Swift, che l'hanno resa un po 'confusa. Ora l'ho riscritta e rimosso tutto ciò che si riferisce a Swift 1.x. Il codice precedente può essere trovato nella cronologia delle modifiche se qualcuno ha bisogno esso.)

Ecco come lo faresti 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)
}

Spiegazioni:

  • A partire da Swift 1.2 (Xcode 6.3), le strutture C importate hanno un inizializzatore predefinito in Swift, che inizializza tutti i campi della struttura a zero, quindi la struttura dell'indirizzo del socket può essere inizializzata con

    var zeroAddress = sockaddr_in()
  • sizeofValue()dà la dimensione di questa struttura, questa deve essere convertita in UInt8per sin_len:

    zeroAddress.sin_len = UInt8(sizeofValue(zeroAddress))
  • AF_INETè un Int32, questo deve essere convertito nel tipo corretto per sin_family:

    zeroAddress.sin_family = sa_family_t(AF_INET)
  • withUnsafePointer(&zeroAddress) { ... }passa l'indirizzo della struttura alla chiusura dove viene utilizzato come argomento per SCNetworkReachabilityCreateWithAddress(). La UnsafePointer($0) conversione è necessaria perché quella funzione si aspetta un puntatore a sockaddr, no sockaddr_in.

  • Il valore restituito da withUnsafePointer()è il valore restituito da SCNetworkReachabilityCreateWithAddress()e che ha il tipo SCNetworkReachability?, ovvero è un opzionale. L' guard letistruzione (una nuova funzionalità in Swift 2.0) assegna il valore non racchiuso alla defaultRouteReachabilityvariabile se non lo è nil. Altrimenti il elseblocco viene eseguito e la funzione ritorna.

  • A partire da Swift 2, SCNetworkReachabilityCreateWithAddress()restituisce un oggetto gestito. Non devi rilasciarlo esplicitamente.
  • A partire da Swift 2, è SCNetworkReachabilityFlagsconforme a OptionSetTypecui ha un'interfaccia simile a un set. Si crea una variabile flag vuota con

    var flags : SCNetworkReachabilityFlags = []

    e controlla le bandiere con

    let isReachable = flags.contains(.Reachable)
    let needsConnection = flags.contains(.ConnectionRequired)
    
  • Il secondo parametro di SCNetworkReachabilityGetFlagsha il tipo UnsafeMutablePointer<SCNetworkReachabilityFlags>, il che significa che devi passare l' indirizzo della variabile flag.

Nota anche che la registrazione di un callback di notifica è possibile a partire da Swift 2, confronta Working with C API from Swift and Swift 2 - UnsafeMutablePointer <Void> to object .


Aggiornamento per Swift 3/4:

I puntatori non sicuri non possono più essere semplicemente convertiti in puntatori di un tipo diverso (vedere - SE-0107 API UnsafeRawPointer ). Di seguito il codice aggiornato:

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

4
@Isuru: UnsafePointer è l'equivalente Swift di un puntatore C. withUnsafePointer(&zeroAddress)chiama la chiusura seguente { ...}con l'indirizzo di zeroAddresscome argomento. All'interno della chiusura, $0sta per questo argomento. - Scusa, è impossibile spiegare tutto questo in poche frasi. Dai un'occhiata alla documentazione sulle chiusure nel libro Swift. $ 0 è un "nome di argomento abbreviato".
Martin R

1
@ JAL: Hai ragione, Apple ha cambiato il modo in cui un "booleano" viene mappato su Swift. Grazie per il tuo feedback, aggiornerò la risposta di conseguenza.
Martin R

1
Questo ritorna truese il wifi non è connesso e il 4G è acceso ma l'utente ha specificato che l'app potrebbe non utilizzare i dati del cellulare .. Eventuali soluzioni?
Max Chuquimia

5
@Jugale: potresti fare qualcosa del tipo: let cellular = flags.contains(.IsWWAN) potresti restituire una coppia invece di un booleano, come: func connectedToNetwork() -> (connected: Bool, cellular: Bool)
EdFunke

3
@Tejas: è possibile utilizzare qualsiasi indirizzo IP invece dell '"indirizzo zero" o utilizzare SCNetworkReachabilityCreateWithName () con un nome host come stringa. Si noti però che SCNetworkReachability controlla solo che un pacchetto inviato a quell'indirizzo possa lasciare il dispositivo locale. Non garantisce che il pacchetto di dati verrà effettivamente ricevuto dall'host.
Martin R

12

Swift 3, IPv4, IPv6

Basato sulla risposta di 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
Lavorando per me anche il modo migliore per NET64 / IPV6, non dimenticare diimport SystemConfiguration
Bhavin_m

@juanjo, come si imposta un host che si desidera raggiungere utilizzando il codice
user2924482

6

Questo non ha nulla a che fare con Swift, ma la soluzione migliore è NON usare Raggiungibilità per determinare se la rete è online. Effettua la connessione e gestisci gli errori se fallisce. Effettuare una connessione a volte può accendere le radio offline dormienti.

L'unico utilizzo valido di Raggiungibilità è utilizzarlo per avvisarti quando una rete passa da offline a online. A quel punto dovresti ritentare le connessioni non riuscite.



Ancora buggy. Basta fare la connessione e gestire gli errori. Vedi openradar.me/21581686 e mail-archive.com/macnetworkprog@lists.apple.com/msg00200.html e il primo commento qui mikeash.com/pyblog/friday-qa-2013-06-14-reachability.html
EricS

Non capisco - non vorresti sapere se eri su WiFi o 3G prima di tentare un grosso caricamento?
Dumbledad

3
Storicamente, Raggiungibilità non funzionava se le radio erano spente. Non l'ho testato su dispositivi moderni in iOS 9, ma garantisco che causava errori di caricamento nelle versioni precedenti di iOS quando la semplice connessione avrebbe funzionato bene. Se desideri che un caricamento avvenga solo tramite WiFi, dovresti utilizzare l' NSURLSessionAPI con NSURLSessionConfiguration.allowsCellularAccess = false.
EricS

3

La soluzione migliore è usare ReachabilitySwift classe , scritta Swift 2e usi SCNetworkReachabilityRef.

Semplice e facile:

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

Funziona come un fascino.

Godere


7
Preferisco la risposta accettata in quanto non richiede l'integrazione di dipendenze di terze parti. Inoltre, questo non risponde alla domanda su come utilizzare la SCNetworkReachabilityclasse in Swift, è un suggerimento di una dipendenza da utilizzare per verificare una connessione di rete valida.
JAL

1

ha aggiornato la risposta di juanjo per creare un'istanza 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)
            }
        })
    }
}

Utilizzo

if Reachability.shared.isConnectedToNetwork(){

}

1

Questo è in Swift 4.0

Sto usando questo 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")
    }
}
}

La schermata reachabilityViewController apparirà se Internet non è presente

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.