Verwendung von SCNetworkReachability in Swift

99

Ich versuche, dieses Code-Snippet in Swift zu konvertieren . Ich habe aufgrund einiger Schwierigkeiten Schwierigkeiten, vom Boden abzuheben.

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

Das erste und wichtigste Problem, das ich habe, ist das Definieren und Arbeiten mit C-Strukturen. In der ersten Zeile ( struct sockaddr_in zeroAddress;) des obigen Codes definieren sie zeroAddressvermutlich eine Instanz, die von der Struktur sockaddr_in (?) Aufgerufen wird. Ich habe versucht, so etwas zu erklären var.

var zeroAddress = sockaddr_in()

Aber ich erhalte den Fehler Fehlendes Argument für den Parameter 'sin_len' im Aufruf, was verständlich ist, da diese Struktur eine Reihe von Argumenten akzeptiert . Also habe ich es erneut versucht.

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

Wie erwartet erhalte ich eine andere Fehlervariable, die innerhalb ihres eigenen Anfangswertes verwendet wird . Ich verstehe auch die Ursache dieses Fehlers. In C deklarieren sie zuerst die Instanz und füllen dann die Parameter aus. Soweit ich weiß, ist das in Swift nicht möglich. An diesem Punkt bin ich wirklich verloren, was ich tun soll.

Ich habe Apples offizielles Dokument über die Interaktion mit C-APIs in Swift gelesen, aber es gibt keine Beispiele für die Arbeit mit Strukturen.

Kann mir bitte jemand hier raushelfen? Ich würde es wirklich schätzen.

Danke dir.


UPDATE: Dank Martin konnte ich das ursprüngliche Problem überwinden. Trotzdem macht es Swift mir nicht leichter. Ich erhalte mehrere neue Fehler.

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: Okay, ich habe diese Zeile in diese geändert,

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

Der neue Fehler, den ich in dieser Zeile erhalte, ist "UnsafePointer" und kann nicht in "CFAllocator" konvertiert werden . Wie kommst du NULLin Swift vorbei?

Auch ich habe diese Zeile geändert und der Fehler ist jetzt weg.

let didRetrieveFlags = SCNetworkReachabilityGetFlags(defaultRouteReachability, &flags)

EDIT 2: Ich habe nildiese Zeile bestanden, nachdem ich diese Frage gesehen habe. Diese Antwort widerspricht jedoch der Antwort hier . Es heißt, es gibt kein Äquivalent zu NULLSwift.

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

Auf jeden Fall erhalte ich eine neue Fehlermeldung, dass 'sockaddr_in' nicht mit 'sockaddr' in der obigen Zeile identisch ist .

Isuru
quelle
Ich habe einen Fehler in der Zeile, wenn! SCNetworkReachabilityGetFlags (defaultRouteReachability & flags) dh unärer Operator! kann nicht auf einen Operanden vom Typ Boolean angewendet werden. . . . Bitte helfen Sie.
Zeebok

Antworten:

236

(Diese Antwort wurde aufgrund von Änderungen in der Swift-Sprache wiederholt erweitert, was sie etwas verwirrend machte. Ich habe sie jetzt umgeschrieben und alles entfernt, was sich auf Swift 1.x bezieht. Der ältere Code kann im Bearbeitungsverlauf gefunden werden, wenn jemand dies benötigt es.)

So würden Sie es in Swift 2.0 (Xcode 7) machen :

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

Erklärungen:

  • Ab Swift 1.2 (Xcode 6.3) haben importierte C-Strukturen einen Standardinitialisierer in Swift, der alle Felder der Struktur auf Null initialisiert, sodass die Socket-Adressstruktur mit initialisiert werden kann

    var zeroAddress = sockaddr_in()
  • sizeofValue()Gibt die Größe dieser Struktur an, muss diese konvertiert werden UInt8für sin_len:

    zeroAddress.sin_len = UInt8(sizeofValue(zeroAddress))
  • AF_INETist ein Int32, dies muss in den richtigen Typ konvertiert werden für sin_family:

    zeroAddress.sin_family = sa_family_t(AF_INET)
  • withUnsafePointer(&zeroAddress) { ... }Übergibt die Adresse der Struktur an den Abschluss, für den sie als Argument verwendet wird SCNetworkReachabilityCreateWithAddress(). Die UnsafePointer($0) Konvertierung ist erforderlich, da diese Funktion einen Zeiger auf erwartet sockaddr, nicht sockaddr_in.

  • Der zurückgegebene Wert withUnsafePointer()ist der Rückgabewert von SCNetworkReachabilityCreateWithAddress()und hat den Typ SCNetworkReachability?, dh er ist optional. Die guard letAnweisung (eine neue Funktion in Swift 2.0) weist der defaultRouteReachabilityVariablen den nicht umschlossenen Wert zu , wenn dies nicht der Fall ist nil. Andernfalls wird der elseBlock ausgeführt und die Funktion kehrt zurück.

  • Gibt ab Swift 2 SCNetworkReachabilityCreateWithAddress()ein verwaltetes Objekt zurück. Sie müssen es nicht explizit freigeben.
  • Ab Swift 2 hat SCNetworkReachabilityFlagskonform OptionSetTypeeine setartige Schnittstelle. Sie erstellen eine leere Flags-Variable mit

    var flags : SCNetworkReachabilityFlags = []

    und suchen Sie nach Flags mit

    let isReachable = flags.contains(.Reachable)
    let needsConnection = flags.contains(.ConnectionRequired)
  • Der zweite Parameter von SCNetworkReachabilityGetFlagshat den Typ UnsafeMutablePointer<SCNetworkReachabilityFlags>, was bedeutet, dass Sie die Adresse der Flags-Variablen übergeben müssen.

Beachten Sie auch, dass das Registrieren eines Notifier-Rückrufs ab Swift 2 möglich ist. Vergleichen Sie Arbeiten mit C-APIs von Swift und Swift 2 - UnsafeMutablePointer <Void> zum Objekt .


Update für Swift 3/4:

Unsichere Zeiger können nicht mehr einfach in einen Zeiger eines anderen Typs konvertiert werden (siehe - SE-0107 UnsafeRawPointer-API ). Hier der aktualisierte 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)
}
Martin R.
quelle
4
@Isuru: UnsafePointer ist das Swift-Äquivalent eines C-Zeigers. withUnsafePointer(&zeroAddress)ruft den folgenden Abschluss { ...}mit der Adresse zeroAddressals Argument auf. In der Schließung $0steht für dieses Argument. - Entschuldigung, es ist unmöglich, das alles in wenigen Sätzen zu erklären. Schauen Sie sich die Dokumentation zu Schließungen im Swift-Buch an. $ 0 ist ein "Kurzargumentname".
Martin R
1
@ JAL: Sie haben Recht, Apple hat geändert, wie ein "Boolescher Wert" Swift zugeordnet wird. Vielen Dank für Ihr Feedback, ich werde die Antwort entsprechend aktualisieren.
Martin R
1
Dies wird zurückgegeben, truewenn WLAN nicht verbunden ist und 4G aktiviert ist, der Benutzer jedoch angegeben hat, dass die App möglicherweise keine Mobilfunkdaten verwendet. Gibt es Lösungen?
Max Chuquimia
5
@ Jugale: Sie könnten etwas tun wie: let cellular = flags.contains(.IsWWAN) Sie können ein Touple anstelle eines Booleschen zurückgeben, wie: func connectedToNetwork() -> (connected: Bool, cellular: Bool)
EdFunke
3
@Tejas: Sie können eine beliebige IP-Adresse anstelle der "Null-Adresse" verwenden oder SCNetworkReachabilityCreateWithName () mit einem Hostnamen als Zeichenfolge verwenden. Beachten Sie jedoch, dass SCNetworkReachability nur überprüft, ob ein an diese Adresse gesendetes Paket das lokale Gerät verlassen kann. Es kann nicht garantiert werden, dass das Datenpaket tatsächlich vom Host empfangen wird.
Martin R
12

Swift 3, IPv4, IPv6

Basierend auf der Antwort von 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
quelle
2
Arbeiten Sie für mich auch am besten für NET64 / IPV6, vergessen Sie nichtimport SystemConfiguration
Bhavin_m
@ juanjo, wie legen Sie einen Host fest, den Sie mit Ihrem Code erreichen möchten
user2924482
6

Dies hat nichts mit Swift zu tun, aber die beste Lösung besteht darin, die Erreichbarkeit NICHT zu verwenden, um festzustellen, ob das Netzwerk online ist. Stellen Sie einfach Ihre Verbindung her und behandeln Sie Fehler, wenn dies fehlschlägt. Das Herstellen einer Verbindung kann manchmal die ruhenden Offline-Funkgeräte starten.

Die einzig gültige Verwendung von Reachability besteht darin, Sie zu benachrichtigen, wenn ein Netzwerk von offline zu online wechselt. An diesem Punkt sollten Sie fehlgeschlagene Verbindungen erneut versuchen.

EricS
quelle
Immer noch fehlerhaft. Stellen Sie einfach die Verbindung her und behandeln Sie Fehler. Siehe openradar.me/21581686 und mail-archive.com/[email protected]/msg00200.html und den ersten Kommentar hier mikeash.com/pyblog/friday-qa-2013-06-14-reachability.html
EricS
Ich verstehe das nicht - möchten Sie nicht wissen, ob Sie über WLAN oder 3G verfügen, bevor Sie einen großen Upload versuchen?
Dumbledad
3
In der Vergangenheit funktionierte die Erreichbarkeit nicht, wenn die Funkgeräte ausgeschaltet waren. Ich habe dies auf modernen Geräten in iOS 9 nicht getestet, aber ich garantiere, dass es in früheren Versionen von iOS zu Upload-Fehlern geführt hat, wenn einfach eine Verbindung hergestellt worden wäre. Wenn Sie möchten, dass ein Upload nur über WLAN erfolgt, sollten Sie die NSURLSessionAPI mit verwenden NSURLSessionConfiguration.allowsCellularAccess = false.
EricS
3

Die beste Lösung ist die Verwendung von ReachabilitySwift Klasse , geschrieben Swift 2und verwendet SCNetworkReachabilityRef.

Simpel und einfach:

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

Arbeiten wie ein Zauber.

Genießen

Bonnke
quelle
7
Ich bevorzuge die akzeptierte Antwort, da keine Abhängigkeiten von Drittanbietern integriert werden müssen. Darüber hinaus beantwortet dies nicht die Frage, wie SCNetworkReachabilityKlassen in Swift verwendet werden sollen. Es handelt sich um einen Vorschlag für eine Abhängigkeit, mit der nach einer gültigen Netzwerkverbindung gesucht werden soll.
JAL
1

Die Antwort von Juanjo wurde aktualisiert, um eine Singleton-Instanz zu erstellen

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

Verwendung

if Reachability.shared.isConnectedToNetwork(){

}
anoop4real
quelle
1

Dies ist in Swift 4.0

Ich verwende dieses Framework https://github.com/ashleymills/Reachability.swift
und installiere 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")
    }
}
}

Der Bildschirm ReachabilityViewController wird angezeigt, wenn das Internet nicht vorhanden ist

Sreekanth G.
quelle