Comment utiliser SCNetworkReachability dans Swift

j'essaie de convertir cet extrait de code en Swift. J'ai du mal à décoller à cause de 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;
}

la première et la principale question que je me pose 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 zeroAddress à partir de la structure sockaddr_in (?), Je suppose. J'ai essayé de déclarer un var comme ça.

var zeroAddress = sockaddr_in()

mais j'obtiens l'erreur argument manquant pour le paramètre 'sin_len' dans l'appel qui est compréhensible parce que cette structure prend un certain nombre d'arguments. J'ai donc essayé de 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 à Swift pour autant que je sache. Donc, je suis vraiment perdu à ce point sur ce qu'il faut faire.

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

est-ce que quelqu'un peut m'aider ici? J'ai vraiment l'apprécier.

Merci.


mise à jour: Merci pour Martin, j'ai pu surmonter le problème initial. Mais Swift ne me facilite 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: Okay j'ai changé cette ligne en ceci,

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

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

aussi j'ai changé cette ligne et l'erreur est partie maintenant.

let didRetrieveFlags = SCNetworkReachabilityGetFlags(defaultRouteReachability, &flags)

EDIT 2: j'ai réussi nil dans cette ligne après avoir vu ce question. Mais cette réponse contredit avec la réponse ici . Il est dit qu'il n'y a pas d'équivalent de NULL dans Swift.

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

de toute façon je reçois une nouvelle erreur disant 'sockaddr_in 'n'est pas identique à' sockaddr ' à la ligne ci-dessus.

90
demandé sur Community 2014-09-02 16:17:35

5 réponses

(cette réponse a été prolongée à plusieurs reprises en raison de changements dans le langage Swift, ce qui l'a rendue un peu confuse. Je l'ai réécrit et j'ai supprimé tout ce qui se réfère à Swift 1.x. Le plus ancien code peut être trouvé dans l'historique des modifications si quelqu'un en a besoin.)

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("151900920"))
    }) 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:

  • à partir 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 socket peut être initialisée avec

    var zeroAddress = sockaddr_in()
    
  • sizeofValue() donne la taille de cette structure, ce qui a à convertir en UInt8 pour sin_len :

    zeroAddress.sin_len = UInt8(sizeofValue(zeroAddress))
    
  • AF_INET est un Int32 , qui doit être converti au type correct pour sin_family :

    zeroAddress.sin_family = sa_family_t(AF_INET)
    
  • withUnsafePointer(&zeroAddress) { ... } passe l'adresse du structure à la fermeture où il est utilisé comme argument pour SCNetworkReachabilityCreateWithAddress() . Le UnsafePointer(" 1519150920") la conversion est nécessaire parce que cette fonction attend un pointeur pour sockaddr , pas sockaddr_in .

  • La valeur retournée par withUnsafePointer() est de retour valeur de SCNetworkReachabilityCreateWithAddress() et qui a le tapez SCNetworkReachability? , c'est à dire qu'il est facultatif. La déclaration guard let (une nouvelle fonctionnalité dans Swift 2.0) attribue la valeur non emballée à la variable defaultRouteReachability si elle est pas nil . Sinon le bloc else est exécuté et la fonction retourner.

  • à partir de Swift 2, SCNetworkReachabilityCreateWithAddress() retourne un objet géré. Vous n'êtes pas obligé de le publier explicitement.
  • à partir de Swift 2, SCNetworkReachabilityFlags est conforme à OptionSetType qui a une interface fixe. Vous créez un variable flags vides avec

    var flags : SCNetworkReachabilityFlags = []
    

    et vérifier les drapeaux avec

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

noter aussi que l'enregistrement d'un avis de rappel est possible à partir de Swift 2, comparez travaillant avec les API de Swift et Swift 2 - UnsafeMutablePointer to object .


mise à jour pour Swift 3/4:

les aiguilles dangereuses ne peuvent pas être simplement converties en une aiguille d'un type différent plus (voir- SE-0107 UNSAFERAWPOINTER API ). 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, {
        "151960920".withMemoryRebound(to: sockaddr.self, capacity: 1) {
            SCNetworkReachabilityCreateWithAddress(nil, "151960920")
        }
    }) 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)
}
224
répondu Martin R 2018-03-22 13:10:15

Swift 3, IPv4, IPv6

D'après 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, {
        "151900920".withMemoryRebound(to: sockaddr.self, capacity: 1) {
            SCNetworkReachabilityCreateWithAddress(nil, "151900920")
        }
    })
}

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, {
        "151900920".withMemoryRebound(to: sockaddr.self, capacity: 1) {
            SCNetworkReachabilityCreateWithAddress(nil, "151900920")
        }
    })
}
11
répondu juanjo 2016-12-16 16:30:24

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. Il suffit de faire votre connexion et de gérer les erreurs si elle échoue. Faire une connexion peut parfois allumer les radios hors ligne dormantes.

la seule utilisation valide de la possibilité d'accès est de l'utiliser pour vous prévenir lorsqu'un réseau passe de hors ligne à en ligne. À ce moment-là, vous devriez réessayer les connexions qui ont échoué.

5
répondu EricS 2015-10-15 21:45:59

la meilleure solution est d'utiliser ReachabilitySwift classe , écrit dans 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()

fonctionne comme un charme.

Profiter

4
répondu Bonnke 2015-10-15 11:49:03

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, {
            "151900920".withMemoryRebound(to: sockaddr.self, capacity: 1) {
                SCNetworkReachabilityCreateWithAddress(nil, "151900920")
            }
        })
    }
    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, {
            "151900920".withMemoryRebound(to: sockaddr.self, capacity: 1) {
                SCNetworkReachabilityCreateWithAddress(nil, "151900920")
            }
        })
    }
}

Utilisation

if Reachability.shared.isConnectedToNetwork(){

}
1
répondu anoop4real 2017-05-03 16:01:53