Как использовать 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()
. TheUnsafePointer()
преобразование необходимо, потому что эта функция ожидает указатель наsockaddr
, а неsockaddr_in
.значение, возвращаемое из
withUnsafePointer()
- возвращаемое значение отSCNetworkReachabilityCreateWithAddress()
и это имеет типSCNetworkReachability?
, т. е. это необязательно. Theguard 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(){
}