Jak używać SCNetworkReachability w Swift

99

Próbuję przekonwertować ten fragment kodu na Swift. Z powodu pewnych trudności walczę z oderwaniem się od ziemi.

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

Pierwsza i główna kwestia dotyczy tego, jak definiować i pracować ze strukturami C. Zakładam, że w pierwszym wierszu ( struct sockaddr_in zeroAddress;) powyższego kodu definiują instancję zeroAddresswywołaną ze struktury struct sockaddr_in (?). Próbowałem zadeklarować coś vartakiego.

var zeroAddress = sockaddr_in()

Ale pojawia się błąd Brakujący argument dla parametru „sin_len” w wywołaniu, co jest zrozumiałe, ponieważ ta struktura przyjmuje wiele argumentów. Więc spróbowałem ponownie.

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

Zgodnie z oczekiwaniami pojawia się inna zmienna błędu używana w jej własnej wartości początkowej . Rozumiem też przyczynę tego błędu. W C najpierw deklarują instancję, a następnie wypełniają parametry. O ile wiem, nie jest to możliwe w Swift. W tej chwili naprawdę nie wiem, co robić.

Czytałem oficjalny dokument firmy Apple dotyczący interakcji z interfejsami API języka C w języku Swift, ale nie ma on przykładów pracy ze strukturami.

Czy ktoś może mi tutaj pomóc? Naprawdę to docenię.

Dziękuję Ci.


AKTUALIZACJA: Dzięki Martinowi udało mi się pokonać początkowy problem. Ale Swift nadal mi nie ułatwia. Otrzymuję wiele nowych błędów.

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
}

EDYCJA 1: OK, zmieniłem ten wiersz na ten,

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

Nowy błąd, który otrzymuję w tym wierszu, to „UnsafePointer”, którego nie można zamienić na „CFAllocator” . Jak przejść NULLw Swift?

Zmieniłem też tę linię i błąd zniknął.

let didRetrieveFlags = SCNetworkReachabilityGetFlags(defaultRouteReachability, &flags)

EDYCJA 2: Przeszedłem nilw tej linii po zobaczeniu tego pytania. Ale ta odpowiedź jest sprzeczna z odpowiedzią tutaj . Mówi, że nie ma odpowiednika NULLw Swift.

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

W każdym razie pojawia się nowy błąd mówiący , że „sockaddr_in” nie jest identyczny z „sockaddr” w powyższym wierszu.

Isuru
źródło
Mam błąd w linii, jeśli! SCNetworkReachabilityGetFlags (defaultRouteReachability, & flagi) tj. operator jednoargumentowy! nie można zastosować do operandu typu Boolean. . . . proszę pomóż.
Zeebok

Odpowiedzi:

236

(Ta odpowiedź była wielokrotnie przedłużana z powodu zmian w języku Swift, co spowodowało, że była nieco zagmatwana. Teraz przepisałem ją i usunąłem wszystko, co odnosi się do Swift 1.x. Starszy kod można znaleźć w historii edycji, jeśli ktoś potrzebuje to.)

Oto jak można to zrobić w 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)
}

Objaśnienia:

  • Począwszy od Swift 1.2 (Xcode 6.3), zaimportowane struktury C mają domyślny inicjator w Swift, który inicjalizuje wszystkie pola struktury na zero, więc strukturę adresu gniazda można zainicjować za pomocą

    var zeroAddress = sockaddr_in()
  • sizeofValue()podaje rozmiar tej struktury, którą należy przekonwertować UInt8na sin_len:

    zeroAddress.sin_len = UInt8(sizeofValue(zeroAddress))
  • AF_INETjest an Int32, to należy przekonwertować na właściwy typ dla sin_family:

    zeroAddress.sin_family = sa_family_t(AF_INET)
  • withUnsafePointer(&zeroAddress) { ... }przekazuje adres struktury do zamknięcia, gdzie jest używany jako argument for SCNetworkReachabilityCreateWithAddress(). UnsafePointer($0) Konieczna jest konwersja, ponieważ ta funkcja oczekuje, że wskaźnik na sockaddrnie sockaddr_in.

  • Wartość zwracana z withUnsafePointer()jest wartością zwracaną przez SCNetworkReachabilityCreateWithAddress()i ma typ SCNetworkReachability?, tj. Jest opcjonalna. guard letOświadczenie (nowa funkcja Swift 2.0) przypisuje wartość nieopakowanych do defaultRouteReachabilityzmiennej, jeśli nie jest nil. W przeciwnym razie elseblok jest wykonywany i funkcja zwraca.

  • Począwszy od Swift 2, SCNetworkReachabilityCreateWithAddress()zwraca zarządzany obiekt. Nie musisz go jawnie publikować.
  • Od wersji Swift 2 SCNetworkReachabilityFlagsjest zgodny z OptionSetTypeinterfejsem podobnym do zestawu. Tworzysz pustą zmienną flag za pomocą

    var flags : SCNetworkReachabilityFlags = []

    i sprawdź flagi z

    let isReachable = flags.contains(.Reachable)
    let needsConnection = flags.contains(.ConnectionRequired)
    
  • Drugi parametr SCNetworkReachabilityGetFlagsma typ UnsafeMutablePointer<SCNetworkReachabilityFlags>, co oznacza, że ​​musisz przekazać adres zmiennej flag.

Należy również zauważyć, że rejestracja wywołania zwrotnego powiadamiającego jest możliwa od wersji Swift 2, porównaj pracę z interfejsami API języka C z języków Swift i Swift 2 - UnsafeMutablePointer <Void> z obiektem .


Aktualizacja dla Swift 3/4:

Niebezpiecznych wskaźników nie można już po prostu przekonwertować na wskaźnik innego typu (patrz - SE-0107 UnsafeRawPointer API ). Tutaj zaktualizowany kod:

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.
źródło
4
@Isuru: UnsafePointer to Swift odpowiednik wskaźnika C. withUnsafePointer(&zeroAddress)wywołuje następujące zamknięcie { ...}z adresem zeroAddressas argument. Wewnątrz zamknięcia $0oznacza ten argument. - Przepraszam, nie da się tego wszystkiego wyjaśnić w kilku zdaniach. Zapoznaj się z dokumentacją dotyczącą zamknięć w książce Swift. $ 0 to „skrócona nazwa argumentu”.
Martin R
1
@JAL: Masz rację, Apple zmienił sposób mapowania „Boolean” na Swift. Dziękuję za twoją opinię, odpowiednio zaktualizuję odpowiedź.
Martin R
1
To zwraca, truejeśli Wi-Fi nie jest podłączone i 4G jest włączone, ale użytkownik określił, że aplikacja może nie korzystać z danych komórkowych. Jakieś rozwiązania?
Max Chuquimia
5
@Jugale: Możesz zrobić coś takiego: let cellular = flags.contains(.IsWWAN) Możesz zwrócić tarę zamiast wartości logicznej, na przykład: func connectedToNetwork() -> (connected: Bool, cellular: Bool)
EdFunke
3
@Tejas: Możesz użyć dowolnego adresu IP zamiast „adresu zerowego” lub użyć SCNetworkReachabilityCreateWithName () z nazwą hosta jako ciągiem znaków. Należy jednak pamiętać, że SCNetworkReachability sprawdza tylko, czy pakiet wysłany na ten adres może opuścić urządzenie lokalne. Nie gwarantuje, że pakiet danych zostanie faktycznie odebrany przez hosta.
Martin R
12

Swift 3, IPv4, IPv6

Na podstawie odpowiedzi Martina 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
źródło
2
Pracując dla mnie również najlepszy sposób na NET64 / IPV6, nie zapomnijimport SystemConfiguration
Bhavin_m
@juanjo, jak ustawić hosta, z którym chcesz się
połączyć
6

Nie ma to nic wspólnego z Swift, ale najlepszym rozwiązaniem jest NIEUŻYWANIE Zasięg do określenia, czy sieć jest online. Po prostu nawiąż połączenie i napraw błędy, jeśli się nie powiedzie. Nawiązanie połączenia może czasami spowodować odpalenie nieaktywnych radiotelefonów.

Jedynym ważnym zastosowaniem Reachability jest używanie go do powiadamiania Cię o przejściu sieci z trybu offline do online. W tym momencie należy ponowić nieudane połączenia.

EricS
źródło
Wciąż wadliwy. Wystarczy nawiązać połączenie i obsłużyć błędy. Zobacz openradar.me/21581686 i mail-archive.com/[email protected]/msg00200.html oraz pierwszy komentarz tutaj mikeash.com/pyblog/friday-qa-2013-06-14-reachability.html
EricS
Nie rozumiem - czy nie chciałbyś wiedzieć, czy korzystałeś z Wi-Fi czy 3G przed próbą dużego przesyłania?
dumbledad
3
Historycznie rzecz biorąc, dostępność nie działała, gdy radia były wyłączone. Nie testowałem tego na nowoczesnych urządzeniach z iOS 9, ale gwarantuję, że powodowało to błędy przesyłania we wcześniejszych wersjach iOS, gdy zwykłe nawiązanie połączenia działałoby dobrze. Jeśli chcesz, aby przesyłanie odbywało się tylko przez Wi-Fi, powinieneś użyć NSURLSessioninterfejsu API z NSURLSessionConfiguration.allowsCellularAccess = false.
EricS
3

Najlepszym rozwiązaniem jest użycie ReachabilitySwift klasy , napisów Swift 2i zastosowań SCNetworkReachabilityRef.

Proste i łatwe:

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

Działa jak urok.

Cieszyć się

Bonnke
źródło
7
Wolę zaakceptowaną odpowiedź, ponieważ nie wymaga integracji żadnych zależności innych firm. Ponadto nie odpowiada to na pytanie, jak używać SCNetworkReachabilityklasy w Swift, jest to sugestia zależności, której należy użyć do sprawdzenia prawidłowego połączenia sieciowego.
JAL
1

zaktualizował odpowiedź juanjo, aby utworzyć pojedynczą instancję

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

Stosowanie

if Reachability.shared.isConnectedToNetwork(){

}
anoop4real
źródło
1

To jest w Swift 4.0

Używam tego frameworka https://github.com/ashleymills/Reachability.swift
i instaluję Pod ..
W 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")
    }
}
}

Jeśli nie ma internetu, pojawi się ekran accessabilityViewController

Sreekanth G
źródło