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

Я пытаюсь преобразовать этот фрагмент кода в 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 из struct 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)

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

Я прочитал официальный представитель Apple document при взаимодействии с C API в 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 в Swift?

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

let didRetrieveFlags = SCNetworkReachabilityGetFlags(defaultRouteReachability, &flags)

EDIT 2: Я прошел nil в этой строке, посмотрев этот вопрос. Но этот ответ противоречит answer здесь. Он говорит, что в Swift нет эквивалента NULL.

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

В любом случае я получаю новую ошибку: 'sockaddr_in' не совпадает с 'sockaddr' в указанной строке.

Ответ 1

(Этот ответ неоднократно расширялся из-за изменений в языке 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 который имеет интерфейс, подобный множеству. Вы создаете пустую переменную flags

    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> с объектом.


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

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

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

Ответ 2

Swift 3, IPv4, IPv6

На основании ответа 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)
        }
    })
}

Ответ 3

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

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

Ответ 4

Лучшее решение - использовать 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()

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

Enjoy

Ответ 5

обновленный ответ 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(){

}

Ответ 6

Это в 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 появится, если нет интернета