Как использовать 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 из структуры 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 документ при взаимодействии с 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
}

изменить 1: хорошо, я изменил эту строку на эту,

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

новая ошибка, которую я получаю в этой строке, - 'UnsafePointer' не конвертируется в 'CFAllocator'. Как вам пройти NULL в Swift?

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

let didRetrieveFlags = SCNetworkReachabilityGetFlags(defaultRouteReachability, &flags)

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

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

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

5 ответов


(этот ответ был неоднократно расширен из-за изменений в языке 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())
    }) 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(). The UnsafePointer() преобразование необходимо, потому что эта функция ожидает указатель на sockaddr, а не sockaddr_in.

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

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

    var flags : SCNetworkReachabilityFlags = []
    

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

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

обратите внимание также, что регистрация обратного вызова notifier возможна с Swift 2, сравнить работа с API C от Swift и Swift 2-UnsafeMutablePointer для объекта.


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

небезопасные указатели нельзя просто преобразовать в указатель a другой тип больше (см. - ЮВ-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, {
        .withMemoryRebound(to: sockaddr.self, capacity: 1) {
            SCNetworkReachabilityCreateWithAddress(nil, )
        }
    }) 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 3, IPv4, IPv6

на основе ответа Мартина 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, {
        .withMemoryRebound(to: sockaddr.self, capacity: 1) {
            SCNetworkReachabilityCreateWithAddress(nil, )
        }
    })
}

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, {
        .withMemoryRebound(to: sockaddr.self, capacity: 1) {
            SCNetworkReachabilityCreateWithAddress(nil, )
        }
    })
}

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

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


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

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

наслаждайтесь


обновлен ответ 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, {
            .withMemoryRebound(to: sockaddr.self, capacity: 1) {
                SCNetworkReachabilityCreateWithAddress(nil, )
            }
        })
    }
    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, {
            .withMemoryRebound(to: sockaddr.self, capacity: 1) {
                SCNetworkReachabilityCreateWithAddress(nil, )
            }
        })
    }
}

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

if Reachability.shared.isConnectedToNetwork(){

}