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

625   5  

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(). Элемент UnsafePointer() преобразование необходимо, потому что эта функция ожидает указатель на 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 to object.


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

небезопасные указатели не могут быть просто преобразованы в указатель a другой тип больше (см. - 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, {
        .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 для создания одноэлементного экземпляра

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

}

Comments

    Ничего не найдено.