web-dev-qa-db-fra.com

Comment utiliser SCNetworkReachability dans Swift

J'essaie de convertir ceci extrait de code en Swift. J'ai du mal à décoller en raison 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 je me pose concerne la définition et l’utilisation 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 zeroAddress à partir de la struct sockaddr_in (?), je suppose. J'ai essayé de déclarer un var comme celui-ci.

var zeroAddress = sockaddr_in()

Mais j'obtiens l'erreur L'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 essayé à nouveau.

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 erreur Variable 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 autant que je sache. Donc, je suis vraiment perdu à ce stade-ci sur ce qu'il faut faire.

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

Quelqu'un peut-il m'aider s'il vous plaît ici? Je l'apprécierais vraiment.

Je vous remercie.


UPDATE: Grâce à Martin, j'ai pu surmonter le problème initial. Mais toujours Swift ne me facilite pas la tâche. Je reçois de nombreuses 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: D'accord, j'ai changé cette ligne en ceci,

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

La nouvelle erreur que j'obtiens sur cette ligne est 'UnsafePointer' n'est pas convertible en 'CFAllocator'. Comment vous passez NULL dans Swift?

Aussi j'ai changé cette ligne et l'erreur est parti maintenant.

let didRetrieveFlags = SCNetworkReachabilityGetFlags(defaultRouteReachability, &flags)

EDIT 2: J'ai passé nil dans cette ligne après avoir vu this question. Mais cette réponse est en contradiction avec la réponse ici . Il dit qu'il n'y a pas d'équivalent à NULL dans Swift.

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

Quoi qu'il en soit, je reçois une nouvelle erreur disant 'sockaddr_in' n'est pas identique à 'sockaddr' à la ligne ci-dessus.

97
Isuru

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

Voici comment vous procéderiez 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:

  • À compter de 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, celle-ci doit être convertie en UInt8 pour sin_len:

    zeroAddress.sin_len = UInt8(sizeofValue(zeroAddress))
    
  • AF_INET Est 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ù il est utilisé comme argument pour SCNetworkReachabilityCreateWithAddress(). La conversion de UnsafePointer($0) est nécessaire car cette fonction attend un pointeur sur sockaddr et non sur sockaddr_in.

  • La valeur renvoyée par withUnsafePointer() est la valeur renvoyée par SCNetworkReachabilityCreateWithAddress() et qui a pour type SCNetworkReachability?, C’est-à-dire une valeur facultative. L'instruction guard let (Une nouvelle fonctionnalité de Swift 2.0) assigne la valeur non enveloppée à la variable defaultRouteReachability si ce n'est pas nil. Sinon le bloc else est exécuté et la fonction retourne.

  • A partir de Swift 2, SCNetworkReachabilityCreateWithAddress()] renvoie un objet géré. Vous n'avez pas à le publier explicitement.
  • À partir de Swift 2, SCNetworkReachabilityFlags se conforme à OptionSetType qui possède une interface de type jeu. Vous créez une variable de drapeau vide avec

    var flags : SCNetworkReachabilityFlags = []
    

    et vérifier les drapeaux avec

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

Notez également que l’enregistrement d’un rappel par notifiant est possible à partir de Swift 2, comparez tilisation des API C de Swift et Swift 2 - UnsafeMutablePointer <Void> à objet .


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 - API SE-0107 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)
}
232
Martin R

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

Cela n’a rien à voir avec Swift, mais la meilleure solution consiste à NE PAS utiliser Reachability pour déterminer si le réseau est en ligne. Établissez 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 en veille.

La seule utilisation valable de Reachability consiste à l’utiliser pour vous avertir lorsqu’un réseau passe de la connexion hors connexion à la connexion en ligne. À ce stade, vous devez réessayer les connexions échouées.

6
EricS

La meilleure solution consiste à utiliser ReachabilitySwiftclass , écrit en Swift 2, et utilise 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

3
Bonnke

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

}
1
anoop4real

Ceci est dans Swift 4.0

J'utilise ce cadre https://github.com/ashleymills/Reachability.Swift
Et installez 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 reachabilityViewController apparaîtra si Internet n'est pas là

1
Sreekanth G