Как использовать SCNetworkReachability в Swift


99

Я пытаюсь преобразовать этот фрагмент кода в Swift. Я изо всех сил пытаюсь оторваться от земли из-за некоторых трудностей.

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

Первая и основная проблема, с которой я столкнулся, заключается в том, как определять структуры C. struct sockaddr_in zeroAddress;Я полагаю, что в первой строке ( ) приведенного выше кода они определяют экземпляр, вызываемый zeroAddressиз структуры sockaddr_in (?). Я пробовал объявить varвот так.

var zeroAddress = sockaddr_in()

Но я получаю сообщение об ошибке « Отсутствует аргумент для параметра sin_len» в вызове, что понятно, потому что эта структура принимает ряд аргументов. Я попробовал еще раз.

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

Как и ожидалось, я получаю другую переменную ошибки, используемую в собственном начальном значении . Я тоже понимаю причину этой ошибки. В C они сначала объявляют экземпляр, а затем заполняют параметры. Насколько я знаю, это невозможно в Swift. Так что я действительно не знаю, что мне делать.

Я прочитал официальный документ Apple о взаимодействии с C API в Swift, но в нем нет примеров работы со структурами.

Может ли кто-нибудь помочь мне здесь? Я был бы очень признателен.

Спасибо.


ОБНОВЛЕНИЕ: Благодаря Мартину я смог решить первоначальную проблему. Но все же Swift не облегчает мне задачу. У меня несколько новых ошибок.

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
}

РЕДАКТИРОВАТЬ 1: Хорошо, я изменил эту строку на эту,

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

Новая ошибка, которую я получаю в этой строке, - «UnsafePointer» не может быть преобразована в «CFAllocator» . Как пройтиNULL на Swift?

Также я изменил эту строку, и теперь ошибка исчезла.

let didRetrieveFlags = SCNetworkReachabilityGetFlags(defaultRouteReachability, &flags)

РЕДАКТИРОВАТЬ 2: Я пропустил nilэту строку, увидев этот вопрос. Но этот ответ противоречит приведенному здесь ответу . В нем говорится, что NULLв Swift нет эквивалента .

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

В любом случае я получаю новую ошибку, в которой говорится, что «sockaddr_in» не идентично «sockaddr» в строке выше.


У меня ошибка в строке, если! SCNetworkReachabilityGetFlags (defaultRouteReachability, & flags) то есть унарный оператор! не может применяться к операнду типа Boolean. . . . пожалуйста помоги.
Zeebok

Ответы:


236

(Этот ответ неоднократно расширялся из-за изменений в языке Swift, что немного сбивало с толку. Теперь я переписал его и удалил все, что относится к Swift 1.x. Старый код можно найти в истории редактирования, если кому-то понадобится Это.)

Вот как вы это сделаете в 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)
}

Пояснения:

  • Начиная с Swift 1.2 (Xcode 6.3), импортированные структуры C имеют инициализатор по умолчанию в Swift, который инициализирует все поля структуры нулями, поэтому структура адреса сокета может быть инициализирована с помощью

    var zeroAddress = sockaddr_in()
  • sizeofValue()дает размер этой структуры, это должно быть преобразовано в UInt8для sin_len:

    zeroAddress.sin_len = UInt8(sizeofValue(zeroAddress))
  • AF_INET- Int32это необходимо преобразовать в правильный тип для sin_family:

    zeroAddress.sin_family = sa_family_t(AF_INET)
  • withUnsafePointer(&zeroAddress) { ... }передает адрес структуры замыканию, где он используется в качестве аргумента для SCNetworkReachabilityCreateWithAddress(). UnsafePointer($0) Преобразование необходимо , потому что функция ожидает указатель sockaddr, а не sockaddr_in.

  • Значение, возвращаемое из, withUnsafePointer()является возвращаемым значением из SCNetworkReachabilityCreateWithAddress()и имеет тип SCNetworkReachability?, т. Е. Является необязательным. guard letЗаявление (новая функция Swift 2.0) присваивает развернутое значение к defaultRouteReachabilityпеременному , если она не является nil. В противном случае elseблок выполняется и функция возвращается.

  • Начиная с Swift 2, SCNetworkReachabilityCreateWithAddress()возвращает управляемый объект. Вам не нужно выпускать его явно.
  • Начиная с Swift 2, SCNetworkReachabilityFlagsсоответствует OptionSetTypeинтерфейсу, подобному множеству. Вы создаете пустую переменную флагов с помощью

    var flags : SCNetworkReachabilityFlags = []

    и проверьте наличие флагов с помощью

    let isReachable = flags.contains(.Reachable)
    let needsConnection = flags.contains(.ConnectionRequired)
  • Второй параметр SCNetworkReachabilityGetFlagsимеет тип UnsafeMutablePointer<SCNetworkReachabilityFlags>, что означает, что вы должны передать адрес переменной flags.

Также обратите внимание, что регистрация обратного вызова уведомителя возможна в Swift 2, сравните Работа с API C из Swift и Swift 2 - UnsafeMutablePointer <Void> с object .


Обновление для Swift 3/4:

Небезопасные указатели больше не могут быть просто преобразованы в указатель другого типа (см. - SE-0107 UnsafeRawPointer API ). Вот обновленный код:

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

4
@Isuru: UnsafePointer - это Swift-эквивалент указателя C. withUnsafePointer(&zeroAddress)вызывает следующее закрытие { ...}с адресом в zeroAddressкачестве аргумента. Внутри закрытия $0стоит этот аргумент. - Извините, все это невозможно объяснить в нескольких предложениях. Посмотрите документацию о закрытии в книге Swift. $ 0 - это «сокращенное имя аргумента».
Martin R

1
@JAL: Вы правы, Apple изменила способ сопоставления логических значений Swift. Спасибо за отзыв, я обновлю ответ соответственно.
Martin R

1
Это возвращается, trueесли Wi-Fi не подключен и 4G включен, но пользователь указал, что приложение не может использовать сотовые данные. Какие-либо решения?
Макс Чукимия

5
@Jugale: Вы могли бы сделать что-то вроде: let cellular = flags.contains(.IsWWAN) вы можете вернуть знак вместо логического, например: func connectedToNetwork() -> (connected: Bool, cellular: Bool)
EdFunke

3
@Tejas: вы можете использовать любой IP-адрес вместо «нулевого адреса» или использовать SCNetworkReachabilityCreateWithName () с именем хоста в виде строки. Но обратите внимание, что SCNetworkReachability проверяет только то, что пакет, отправленный на этот адрес, может покинуть локальное устройство. Это не гарантирует, что пакет данных действительно будет получен хостом.
Martin R

12

Swift 3, IPv4, IPv6

На основе ответа Мартина Р.

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

2
Работает для меня также лучший способ для NET64 / IPV6, не забудьтеimport SystemConfiguration
Bhavin_m

@juanjo, как установить хост, к
которому

6

Это не имеет ничего общего со Swift, но лучшее решение - НЕ использовать Reachability для определения, находится ли сеть в сети. Просто установите соединение и обработайте ошибки в случае сбоя. Установление соединения может иногда запускать бездействующие автономные радиостанции.

Единственное допустимое использование Reachability - использовать ее для уведомления вас, когда сеть переходит из автономного режима в онлайн. На этом этапе вы должны повторить неудачные попытки подключения.



Все еще глючит. Просто установите соединение и обработайте ошибки. См. Openradar.me/21581686 и mail-archive.com/macnetworkprog@lists.apple.com/msg00200.html и первый комментарий здесь mikeash.com/pyblog/friday-qa-2013-06-14-reachability.html
EricS

Я этого не понимаю - вы бы хотели знать, подключены ли вы к сети Wi-Fi или 3G, прежде чем пытаться выполнить большую загрузку?
Дамблдад

3
Исторически сложилось так, что Reachability не работала, если радиостанции были выключены. Я не тестировал это на современных устройствах в iOS 9, но гарантирую, что раньше это приводило к сбоям при загрузке в более ранних версиях iOS, когда простое соединение работало нормально. Если вы хотите, чтобы загрузка осуществлялась только через Wi-Fi, вам следует использовать NSURLSessionAPI с NSURLSessionConfiguration.allowsCellularAccess = false.
EricS

3

Лучшее решение - использовать ReachabilitySwift класс , написанный на Swift 2и использующийSCNetworkReachabilityRef .

Просто и легко:

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

Работает как шарм.

наслаждаться


7
Я предпочитаю принятый ответ, поскольку он не требует интеграции каких-либо сторонних зависимостей. Кроме того, это не отвечает на вопрос о том, как использовать SCNetworkReachabilityкласс в Swift, это предложение о зависимости, которую следует использовать для проверки действительного сетевого подключения.
JAL

1

обновлен ответ juanjo для создания экземпляра 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)
            }
        })
    }
}

использование

if Reachability.shared.isConnectedToNetwork(){

}

1

Это в Swift 4.0

Я использую этот фреймворк https://github.com/ashleymills/Reachability.swift
и устанавливаю Pod ..
В 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")
    }
}
}

Экран reachabilityViewController появится, если интернета нет


0

Swift 5, используя NWPathMonitor

import Network

func configureNetworkMonitor(){
        let monitor = NWPathMonitor()
        
        monitor.pathUpdateHandler = { path in
            
            if path.status != .satisfied {
                print("not connected")
            }
            else if path.usesInterfaceType(.cellular) {
                print("Cellular")
            }
            else if path.usesInterfaceType(.wifi) {
                print("WIFI")
            }
            else if path.usesInterfaceType(.wiredEthernet) {
                print("Ethernet")
            }
            else if path.usesInterfaceType(.other){
                print("Other")
            }else if path.usesInterfaceType(.loopback){
                print("Loop Back")
            }
        }
        
        monitor.start(queue: DispatchQueue.global(qos: .background))
    }
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.