Как использовать 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.