Comment utiliser SCNetworkReachability dans Swift

99

J'essaye de convertir cet extrait de code en Swift. J'ai du mal à décoller à cause de certaines difficultés.

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

Le premier et le principal problème que j'ai est de savoir comment définir et travailler avec des structures C. Dans la première ligne ( struct sockaddr_in zeroAddress;) du code ci-dessus, je pense qu'ils définissent une instance appelée à zeroAddresspartir de la structure sockaddr_in (?), Je suppose. J'ai essayé de déclarer un varcomme ça.

var zeroAddress = sockaddr_in()

Mais j'obtiens l'erreur Argument manquant pour le paramètre 'sin_len' dans l'appel, ce qui est compréhensible car cette structure prend un certain nombre d'arguments. Alors j'ai réessayé.

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

Comme prévu, j'obtiens une autre variable d' erreur utilisée dans sa propre valeur initiale . Je comprends aussi la cause de cette erreur. En C, ils déclarent d'abord l'instance, puis remplissent les paramètres. Ce n'est pas possible dans Swift pour autant que je sache. Je suis donc vraiment perdu à ce stade sur ce qu'il faut faire.

J'ai lu le document officiel d'Apple sur l'interaction avec les API C dans Swift mais il n'a pas d'exemples de travail avec les structures.

Quelqu'un peut-il m'aider ici? J'apprécierais vraiment.

Je vous remercie.


MISE À JOUR: Grâce à Martin, j'ai pu surmonter le problème initial. Mais Swift ne me facilite toujours pas la tâche. Je reçois plusieurs nouvelles erreurs.

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, j'ai changé cette ligne en ceci,

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

La nouvelle erreur que j'obtiens à cette ligne est que «UnsafePointer» n'est pas convertible en «CFAllocator» . Comment passer NULLà Swift?

J'ai également changé cette ligne et l'erreur a disparu maintenant.

let didRetrieveFlags = SCNetworkReachabilityGetFlags(defaultRouteReachability, &flags)

EDIT 2: je suis passé nildans cette ligne après avoir vu cette question. Mais cette réponse est en contradiction avec la réponse ici . Il dit qu'il n'y a pas d'équivalent à NULLSwift.

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

Quoi qu'il en soit, j'obtiens une nouvelle erreur disant que «sockaddr_in» n'est pas identique à «sockaddr» à la ligne ci-dessus.

Isuru
la source
J'ai une erreur à la ligne si! SCNetworkReachabilityGetFlags (defaultRouteReachability, & flags) ie opérateur unaire! ne peut pas être appliqué à un opérande de type Boolean. . . . veuillez aider.
Zeebok

Réponses:

236

(Cette réponse a été étendue à plusieurs reprises en raison de changements dans le langage Swift, ce qui la rendait un peu déroutante. Je l'ai maintenant réécrite et supprimé tout ce qui se réfère à Swift 1.x. L'ancien code peut être trouvé dans l'historique des modifications si quelqu'un a besoin il.)

Voici comment vous le feriez dans 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)
}

Explications:

  • Depuis Swift 1.2 (Xcode 6.3), les structures C importées ont un initialiseur par défaut dans Swift, qui initialise tous les champs de la structure à zéro, de sorte que la structure d'adresse de socket peut être initialisée avec

    var zeroAddress = sockaddr_in()
  • sizeofValue()donne la taille de cette structure, il faut la convertir en UInt8pour sin_len:

    zeroAddress.sin_len = UInt8(sizeofValue(zeroAddress))
  • AF_INETest un Int32, il doit être converti dans le type correct pour sin_family:

    zeroAddress.sin_family = sa_family_t(AF_INET)
  • withUnsafePointer(&zeroAddress) { ... }passe l'adresse de la structure à la fermeture où elle est utilisée comme argument pour SCNetworkReachabilityCreateWithAddress(). La UnsafePointer($0) conversion est nécessaire car cette fonction attend un pointeur vers sockaddr, non sockaddr_in.

  • La valeur renvoyée de withUnsafePointer()est la valeur de retour de SCNetworkReachabilityCreateWithAddress()et qui a le type SCNetworkReachability?, c'est-à-dire qu'elle est facultative. L' guard letinstruction (une nouvelle fonctionnalité de Swift 2.0) attribue la valeur déroulée à la defaultRouteReachabilityvariable si ce n'est pas le cas nil. Sinon, le elsebloc est exécuté et la fonction retourne.

  • À partir de Swift 2, SCNetworkReachabilityCreateWithAddress()renvoie un objet géré. Vous n'êtes pas obligé de le publier explicitement.
  • À partir de Swift 2, SCNetworkReachabilityFlagsest conforme à OptionSetTypequi a une interface de type set. Vous créez une variable d'indicateurs vide avec

    var flags : SCNetworkReachabilityFlags = []

    et vérifiez les drapeaux avec

    let isReachable = flags.contains(.Reachable)
    let needsConnection = flags.contains(.ConnectionRequired)
  • Le deuxième paramètre de SCNetworkReachabilityGetFlagsa le type UnsafeMutablePointer<SCNetworkReachabilityFlags>, ce qui signifie que vous devez passer l' adresse de la variable flags.

Notez également que l'enregistrement d'un rappel de notificateur est possible à partir de Swift 2, comparez Working with C APIs from Swift and Swift 2 - UnsafeMutablePointer <Void> à object .


Mise à jour pour Swift 3/4:

Les pointeurs non sécurisés ne peuvent plus être simplement convertis en un pointeur d'un type différent (voir - SE-0107 API UnsafeRawPointer ). Voici le code mis à jour:

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
la source
4
@Isuru: UnsafePointer est l'équivalent Swift d'un pointeur C. withUnsafePointer(&zeroAddress)appelle la fermeture suivante { ...}avec l'adresse de zeroAddresscomme argument. À l'intérieur de la fermeture, $0représente cet argument. - Désolé, il est impossible d'expliquer tout cela en quelques phrases. Jetez un œil à la documentation sur les fermetures dans le livre Swift. $ 0 est un "nom d'argument abrégé".
Martin R
1
@JAL: Vous avez raison, Apple a changé la façon dont un "booléen" est mappé sur Swift. Merci pour vos commentaires, je mettrai à jour la réponse en conséquence.
Martin R
1
Cela revient truesi le wifi n'est pas connecté et que la 4G est activée mais que l'utilisateur a spécifié que l'application ne peut pas utiliser de données cellulaires. Des solutions?
Max Chuquimia
5
@Jugale: Vous pouvez faire quelque chose comme: let cellular = flags.contains(.IsWWAN) Vous pouvez renvoyer un touple au lieu d'un booléen, comme: func connectedToNetwork() -> (connected: Bool, cellular: Bool)
EdFunke
3
@Tejas: Vous pouvez utiliser n'importe quelle adresse IP au lieu de "l'adresse zéro", ou utiliser SCNetworkReachabilityCreateWithName () avec un nom d'hôte sous forme de chaîne. Mais notez que SCNetworkReachability vérifie uniquement qu'un paquet envoyé à cette adresse peut quitter le périphérique local. Cela ne garantit pas que le paquet de données sera effectivement reçu par l'hôte.
Martin R
12

Swift 3, IPv4, IPv6

Basé sur la réponse 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
la source
2
Travailler pour moi aussi le meilleur moyen pour NET64 / IPV6 aussi, n'oubliez pas deimport SystemConfiguration
Bhavin_m
@juanjo, comment définir un hôte que vous souhaitez atteindre en utilisant votre code
user2924482
6

Cela n'a rien à voir avec Swift, mais la meilleure solution est de NE PAS utiliser l'accessibilité pour déterminer si le réseau est en ligne. Faites simplement votre connexion et gérez les erreurs en cas d'échec. L'établissement d'une connexion peut parfois déclencher les radios hors ligne dormantes.

La seule utilisation valable de l'accessibilité est de l'utiliser pour vous avertir lorsqu'un réseau passe de hors ligne à en ligne. À ce stade, vous devez réessayer les connexions ayant échoué.

EricS
la source
Toujours bogué. Faites simplement la connexion et gérez les erreurs. Voir openradar.me/21581686 et mail-archive.com/[email protected]/msg00200.html et le premier commentaire ici mikeash.com/pyblog/friday-qa-2013-06-14-reachability.html
EricS
Je ne comprends pas - ne voudriez-vous pas savoir si vous étiez sur WiFi ou 3G avant de tenter un gros téléchargement?
dumbledad
3
Historiquement, l'accessibilité ne fonctionnait pas si les radios étaient éteintes. Je ne l'ai pas testé sur les appareils modernes sous iOS 9, mais je garantis que cela provoquait des échecs de téléchargement dans les versions antérieures d'iOS alors qu'une simple connexion aurait bien fonctionné. Si vous souhaitez qu'un téléchargement se fasse uniquement via WiFi, vous devez utiliser l' NSURLSessionAPI avec NSURLSessionConfiguration.allowsCellularAccess = false.
EricS
3

La meilleure solution consiste à utiliser la ReachabilitySwift classe , l'écriture Swift 2et les utilisations SCNetworkReachabilityRef.

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

Travailler comme un charme.

Prendre plaisir

Bonnke
la source
7
Je préfère la réponse acceptée car elle ne nécessite pas l'intégration de dépendances tierces. De plus, cela ne répond pas à la question de savoir comment utiliser la SCNetworkReachabilityclasse dans Swift, c'est une suggestion de dépendance à utiliser pour vérifier une connexion réseau valide.
JAL
1

mise à jour de la réponse de juanjo pour créer une instance 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)
            }
        })
    }
}

Usage

if Reachability.shared.isConnectedToNetwork(){

}
anoop4real
la source
1

C'est dans Swift 4.0

J'utilise ce framework https://github.com/ashleymills/Reachability.swift
et installe le pod ..
Dans 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")
    }
}
}

L'écran d'accessibilitéViewController apparaîtra si Internet n'est pas là

Sreekanth G
la source