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