Как использовать 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;
}

Первый и главный вопрос, который у меня возникает, заключается в том, как определить и работать со структурами Си. В первой строке (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 они сначала объявляют экземпляр, а затем заполняют параметры. Насколько я знаю, в Свифте это невозможно. Так что я действительно потерял в этом вопросе, что делать.

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

РЕДАКТИРОВАТЬ 1: Хорошо, я изменил эту строку на это,

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

Новая ошибка, которую я получаю в этой строке: "UnsafePointer" не конвертируется в "CFAllocator". Как пройти NULL в Свифте?

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

let didRetrieveFlags = SCNetworkReachabilityGetFlags(defaultRouteReachability, &flags)

РЕДАКТИРОВАТЬ 2: я прошел nil в этой строке, увидев этот вопрос. Но этот ответ противоречит ответу здесь. Он говорит, что нет эквивалента NULL в Свифте.

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

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

8 ответов

Решение

(Этот ответ неоднократно расширялся из-за изменений в языке 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, сравните Работу с C API от Swift и Swift 2 - UnsafeMutablePointer с объектом.


Обновление для 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)
}

Swift 5, используя NWPathMonitor

import Network

func configureNetworkMonitor(){
        let monitor = NWPathMonitor()
        
        monitor.pathUpdateHandler = { path in
            
            if path.status != .satisfied {
                print("not connected")
            }
            else if path.usesInterfaceType(.cellular) {
                print("Cellular")
            }
            else if path.usesInterfaceType(.wifi) {
                print("WIFI")
            }
            else if path.usesInterfaceType(.wiredEthernet) {
                print("Ethernet")
            }
            else if path.usesInterfaceType(.other){
                print("Other")
            }else if path.usesInterfaceType(.loopback){
                print("Loop Back")
            }
        }
        
        monitor.start(queue: DispatchQueue.global(qos: .background))
    }

Swift 3, IPv4, IPv6

Основываясь на ответе Мартина Р:

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

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

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

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

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

наслаждаться

SwiftUI использует решение Mithra Sigam, описанное выше:

      import Foundation
import Network

class NetworkReachabilityManager: ObservableObject {
    @Published var networkPathStatus: NWPath.Status
    @Published var availableInterfaces: [NWInterface]
    
    let monitor = NWPathMonitor()
    
    init() {
        monitor.start(queue: DispatchQueue.global(qos: .background))
        
        let currentPath = monitor.currentPath
        
        networkPathStatus = currentPath.status
        availableInterfaces = currentPath.availableInterfaces
        
        monitor.pathUpdateHandler = { [self] networkPath in
            networkPathStatus = networkPath.status
            availableInterfaces = networkPath.availableInterfaces
        }
    }
    
    deinit {
        monitor.cancel()
    }
}

Обновленный ответ дзюньо на создание экземпляра синглтона

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

}

Это в 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")
    }
}
}

Экран появится, если нет интернета

Другие вопросы по тегам