Como usar SCNetworkReachability em Swift

99

Estou tentando converter este trecho de código para Swift. Estou lutando para sair do chão devido a algumas dificuldades.

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

O primeiro e principal problema que estou tendo é como definir e trabalhar com estruturas C. Na primeira linha ( struct sockaddr_in zeroAddress;) do código acima, acho que eles estão definindo uma instância chamada zeroAddressde struct sockaddr_in (?), Presumo. Tentei declarar algo varassim.

var zeroAddress = sockaddr_in()

Mas recebo o erro Argumento ausente para o parâmetro 'sin_len' na chamada, o que é compreensível porque essa estrutura leva vários argumentos. Então, tentei novamente.

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

Como esperado, recebo alguma outra variável de erro usada em seu próprio valor inicial . Eu também entendo a causa desse erro. Em C, eles declaram a instância primeiro e depois preenchem os parâmetros. Não é possível em Swift, tanto quanto eu sei. Portanto, estou realmente perdido neste ponto sobre o que fazer.

Eu li o documento oficial da Apple sobre a interação com APIs C no Swift, mas não há exemplos de como trabalhar com structs.

Alguém pode me ajudar aqui? Eu realmente aprecio isso.

Obrigado.


ATUALIZAÇÃO: Graças a Martin, consegui superar o problema inicial. Mas ainda assim o Swift não está facilitando as coisas para mim. Estou recebendo vários novos erros.

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, mudei esta linha para esta,

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

O novo erro que estou obtendo nesta linha é 'UnsafePointer' não é conversível para 'CFAllocator' . Como você passaNULL em Swift?

Também mudei essa linha e o erro desapareceu agora.

let didRetrieveFlags = SCNetworkReachabilityGetFlags(defaultRouteReachability, &flags)

EDIT 2: Passei nilnesta linha depois de ver esta questão. Mas essa resposta contradiz a resposta aqui . Diz que não há equivalente NULLem Swift.

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

De qualquer forma, recebo um novo erro dizendo que 'sockaddr_in' não é idêntico a 'sockaddr' na linha acima.

Isuru
fonte
Estou tendo um erro na linha se! SCNetworkReachabilityGetFlags (defaultRouteReachability, & flags), ou seja, operador unário! não pode ser aplicado a um operando do tipo Boolean. . . . por favor ajude.
Zeebok de

Respostas:

236

(Esta resposta foi estendida repetidamente devido a mudanças na linguagem Swift, o que a tornou um pouco confusa. Agora eu a reescrevi e removi tudo o que se refere a Swift 1.x. O código mais antigo pode ser encontrado no histórico de edição se alguém precisar isto.)

É assim que você faria no 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)
}

Explicações:

  • A partir do Swift 1.2 (Xcode 6.3), as estruturas C importadas têm um inicializador padrão em Swift, que inicializa todos os campos da estrutura para zero, de modo que a estrutura de endereço do socket pode ser inicializada com

    var zeroAddress = sockaddr_in()
  • sizeofValue()dá o tamanho desta estrutura, isso deve ser convertido UInt8para sin_len:

    zeroAddress.sin_len = UInt8(sizeofValue(zeroAddress))
  • AF_INETé um Int32, isso deve ser convertido para o tipo correto para sin_family:

    zeroAddress.sin_family = sa_family_t(AF_INET)
  • withUnsafePointer(&zeroAddress) { ... }passa o endereço da estrutura para o fechamento onde é usado como argumento para SCNetworkReachabilityCreateWithAddress(). A UnsafePointer($0) conversão é necessária porque essa função espera um ponteiro para sockaddr, não sockaddr_in.

  • O valor retornado de withUnsafePointer()é o valor de retorno de SCNetworkReachabilityCreateWithAddress()e que possui o tipo SCNetworkReachability?, ou seja, é opcional. A guard letinstrução (um novo recurso no Swift 2.0) atribui o valor não empacotado à defaultRouteReachabilityvariável se não for nil. Caso contrário, o elsebloco é executado e a função retorna.

  • A partir do Swift 2, SCNetworkReachabilityCreateWithAddress()retorna um objeto gerenciado. Você não precisa liberá-lo explicitamente.
  • A partir do Swift 2, SCNetworkReachabilityFlagsestá em conformidade com o OptionSetTypequal possui uma interface semelhante a um conjunto. Você cria uma variável de sinalizadores vazia com

    var flags : SCNetworkReachabilityFlags = []

    e verifique se há sinalizadores com

    let isReachable = flags.contains(.Reachable)
    let needsConnection = flags.contains(.ConnectionRequired)
  • O segundo parâmetro de SCNetworkReachabilityGetFlagstem o tipo UnsafeMutablePointer<SCNetworkReachabilityFlags>, o que significa que você deve passar o endereço da variável sinalizadores.

Observe também que registrar um retorno de chamada do notificador é possível a partir do Swift 2, compare Working with C APIs de Swift e Swift 2 - UnsafeMutablePointer <Void> para o objeto .


Atualização para Swift 3/4:

Os ponteiros inseguros não podem ser simplesmente convertidos em um ponteiro de um tipo diferente (consulte - SE-0107 API UnsafeRawPointer ). Aqui está o código atualizado:

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
fonte
4
@Isuru: UnsafePointer é o equivalente do Swift a um ponteiro C. withUnsafePointer(&zeroAddress)chama o encerramento seguinte { ...}com o endereço de zeroAddresscomo argumento. Dentro do fechamento, $0representa esse argumento. - Desculpe, é impossível explicar tudo isso em poucas frases. Dê uma olhada na documentação sobre fechamentos no livro Swift. $ 0 é um "nome de argumento abreviado".
Martin R
1
@JAL: Você está certo, a Apple mudou a forma como um "booleano" é mapeado para o Swift. Obrigado por seus comentários, atualizarei a resposta de acordo.
Martin R
1
Isso retorna truese o wi-fi não estiver conectado e o 4G estiver ativado, mas o usuário tiver especificado que o aplicativo não pode usar dados do celular. Alguma solução?
Max Chuquimia
5
@Jugale: Você poderia fazer algo como: let cellular = flags.contains(.IsWWAN) Você pode retornar um toque em vez de um booleano, como: func connectedToNetwork() -> (connected: Bool, cellular: Bool)
EdFunke
3
@Tejas: Você pode usar qualquer endereço IP em vez do "endereço zero" ou usar SCNetworkReachabilityCreateWithName () com um nome de host como string. Mas observe que SCNetworkReachability apenas verifica se um pacote enviado para aquele endereço pode deixar o dispositivo local. Isso não garante que o pacote de dados seja realmente recebido pelo host.
Martin R
12

Swift 3, IPv4, IPv6

Com base na resposta 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
fonte
2
Trabalhando para mim também é a melhor maneira para NET64 / IPV6 também, não se esqueça deimport SystemConfiguration
Bhavin_m
@juanjo, como você define um host que deseja alcançar usando seu código
user2924482
6

Isso não tem nada a ver com o Swift, mas a melhor solução é NÃO usar o Reachability para determinar se a rede está online. Basta fazer sua conexão e lidar com os erros se ela falhar. Fazer uma conexão às vezes pode ativar os rádios off-line inativos.

O único uso válido de Reachability é usá-lo para notificá-lo quando uma rede muda de offline para online. Nesse ponto, você deve tentar novamente as conexões com falha.

EricS
fonte
Ainda está cheio de erros. Basta fazer a conexão e tratar dos erros. Consulte openradar.me/21581686 e mail-archive.com/[email protected]/msg00200.html e o primeiro comentário aqui mikeash.com/pyblog/friday-qa-2013-06-14-reachability.html
EricS
Eu não entendo - você não gostaria de saber se estava em WiFi ou 3G antes de tentar um grande upload?
dumbledad
3
Historicamente, a acessibilidade não funcionava se os rádios fossem desligados. Não testei isso em dispositivos modernos no iOS 9, mas garanto que costumava causar falhas de upload em versões anteriores do iOS, quando simplesmente fazer uma conexão teria funcionado bem. Se você deseja que um upload seja feito apenas via WiFi, você deve usar a NSURLSessionAPI com NSURLSessionConfiguration.allowsCellularAccess = false.
EricS
3

A melhor solução é usar ReachabilitySwift classe , escrita Swift 2e usaSCNetworkReachabilityRef .

Simples e fácil:

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

Funcionando como um encanto.

Aproveitar

Bonnke
fonte
7
Prefiro a resposta aceita, pois não requer a integração de nenhuma dependência de terceiros. Além disso, isso não responde à questão de como usar a SCNetworkReachabilityclasse em Swift, é uma sugestão de dependência a ser usada para verificar se há uma conexão de rede válida.
JAL de
1

atualizou a resposta de juanjo para criar uma instância 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)
            }
        })
    }
}

Uso

if Reachability.shared.isConnectedToNetwork(){

}
anoop4real
fonte
1

Este é o Swift 4.0

Estou usando esta estrutura https://github.com/ashleymills/Reachability.swift
e instalar o Pod ..
Em 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")
    }
}
}

A tela reachabilityViewController aparecerá se não houver internet

Sreekanth G
fonte