Як використовувати 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 вони спочатку оголошують екземпляр, а потім заповнюють параметри. Наскільки я знаю, у Свіфті це неможливо. Тож я в цей момент по-справжньому програю, що робити.

Я читав офіційний документ Apple про взаємодію з API API C в 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
}

EDIT 1: Гаразд, я змінив цей рядок на цей,

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

Нова помилка, яку я отримую на цій лінії, - це "UnsafePointer", не конвертована у "CFAllocator" . Як вам пройтиNULL у Свіфті?

Також я змінив цей рядок і зараз помилка відсутня.

let didRetrieveFlags = SCNetworkReachabilityGetFlags(defaultRouteReachability, &flags)

EDIT 2: Я пройшов nilцей рядок, побачивши це питання. Але ця відповідь суперечить відповіді тут . Це говорить про те, що NULLу Swift немає рівнозначного .

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

У будь-якому випадку я отримую нову помилку: "sockaddr_in" не є тотожною "sockaddr" у наведеному вище рядку.


У мене виникають помилки в рядку, якщо! SCNetworkReachabilityGetFlags (defaultRouteReachability, & прапори), тобто оператор, що працює в операції! не можна застосувати до операнду типу Boolean. . . . будь ласка, допоможіть.
Зебок

Відповіді:


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, який ініціалізує всі поля stru до нуля, тому структура адреси сокета може бути ініціалізована з

    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?, тобто є необов'язковим. Theguard 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>, що означає, що вам потрібно передати адресу змінної прапорів.

Зауважте також, що реєстрація зворотного дзвінка сповіщувача можлива як у Swift 2, порівняйте роботу з API API C від Swift та Swift 2 - UnsafeMutablePointer <Void> для об'єкта .


Оновлення для 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 - "ім'я скороченого аргументу".
Мартін Р.

1
@JAL: Ви маєте рацію, Apple змінила, як "булевий" відображається на Swift. Дякую за Ваш відгук, відповідь оновлю відповідно.
Мартін Р

1
Це повертається, trueякщо Wi-Fi не підключено та 4G увімкнено, але користувач вказав, що програма не може використовувати стільникові дані. Будь-які рішення?
Макс Чукімія

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

3
@Tejas: Ви можете використовувати будь-яку IP-адресу замість "нульової адреси" або використовувати SCNetworkReachabilityCreateWithName () з ім'ям хоста як рядком. Але зауважте, що SCNetworkReachability лише перевіряє, що пакет, надісланий на цю адресу, може залишити локальний пристрій. Це не гарантує, що пакет даних буде фактично отриманий хостом.
Мартін Р

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, як встановити хоста, до якого потрібно звернутися, використовуючи свій код
user2924482

6

Це не має нічого спільного з Swift, але найкраще рішення - НЕ використовувати Доступність для визначення того, чи є мережа в мережі. Просто зробіть підключення та обробіть помилки, якщо воно не вдасться. Встановлення з'єднання може часом запалювати радіо в режимі спокою в автономному режимі.

Єдине дійсне використання Reachability - це використовувати його для сповіщення про перехід мережі з офлайн до Інтернету. У цей момент вам слід повторити помилкове з'єднання.



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

Я цього не розумію - чи не хочете ви знати, чи були ви на WiFi чи 3G, перш ніж спробувати велике завантаження?
dumbledad

3
Історично доступність не працювала, якщо радіоприймачі були вимкнені. Я не перевіряв це на сучасних пристроях в iOS 9, але гарантую, що він раніше викликав збої в завантаженні в попередніх версіях iOS, коли просто встановлення з'єднання спрацювало б добре. Якщо ви хочете, щоб завантаження відбувалося лише через WiFi, вам слід скористатися 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, щоб створити одиночний екземпляр

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

Якщо Інтернету немає, з'явиться екран доступностіViewController

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.