Swift'de SCNetworkReachability nasıl kullanılır?


99

Bu kod parçacığını Swift'e dönüştürmeye çalışıyorum . Bazı zorluklardan dolayı yerden kalkmakta zorlanıyorum.

- (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;
}

Yaşadığım ilk ve ana sorun, C yapılarını nasıl tanımlayacağım ve onlarla nasıl çalışacağım. struct sockaddr_in zeroAddress;Yukarıdaki kodun ilk satırında ( ), zeroAddresssockaddr_in (?) Struct yapısından çağrılan bir örneği tanımladıklarını düşünüyorum . varBöyle bir açıklama yapmayı denedim .

var zeroAddress = sockaddr_in()

Ama çağrıda 'sin_len' parametresi için eksik argüman hatası alıyorum , bu anlaşılabilir bir durum çünkü bu yapı bir dizi argüman alıyor. Ben de tekrar denedim.

var zeroAddress = sockaddr_in(sin_len: sizeof(zeroAddress), sin_family: AF_INET, sin_port: nil, sin_addr: nil, sin_zero: nil)

Beklendiği gibi, kendi başlangıç ​​değerinde kullanılan başka bir hata Değişkeni alıyorum . Bu hatanın nedenini de anlıyorum. C'de, önce örneği bildirirler ve ardından parametreleri doldururlar. Bildiğim kadarıyla Swift'de bu mümkün değil. Yani ne yapacağım konusunda bu noktada gerçekten kayboldum.

Apple'ın Swift'deki C API'leriyle etkileşimle ilgili resmi belgesini okudum , ancak yapılarla çalışma konusunda hiçbir örneği yok.

Biri bana yardım edebilir mi lütfen? Gerçekten minnettar olurum.

Teşekkür ederim.


GÜNCELLEME: Martin sayesinde ilk problemi aşabildim. Ama yine de Swift bunu benim için kolaylaştırmıyor. Birden çok yeni hata alıyorum.

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
}

DÜZENLEME 1: Tamam, bu satırı buna değiştirdim,

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

Bu satırda aldığım yeni hata 'UnsafePointer', 'CFAllocator'a dönüştürülemiyor . Swift'den nasıl geçersiniz NULL?

Ayrıca bu satırı değiştirdim ve hata artık ortadan kalktı.

let didRetrieveFlags = SCNetworkReachabilityGetFlags(defaultRouteReachability, &flags)

DÜZENLEME 2: Bu soruyu nilgördükten sonra bu satırda geçtim . Fakat bu cevap buradaki cevapla çelişiyor . Swift'de bunun muadili olmadığını söylüyor .NULL

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

Her neyse , yukarıdaki satırda 'sockaddr_in' ile 'sockaddr' aynı olmadığını söyleyen yeni bir hata alıyorum .


if! SCNetworkReachabilityGetFlags (defaultRouteReachability, & flaglar) yani tekli operatör! Boolean türünde bir işlenene uygulanamaz. . . . lütfen yardım et.
Zeebok

Yanıtlar:


236

(Bu yanıt, Swift dilinde yapılan değişiklikler nedeniyle tekrar tekrar uzatıldı, bu da biraz kafa karıştırıcı hale geldi. Şimdi yeniden yazdım ve Swift 1.x'e atıfta bulunan her şeyi kaldırdım. o.)

Swift 2.0'da (Xcode 7) bunu şu şekilde yaparsınız :

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

Açıklamalar:

  • Swift 1.2'den (Xcode 6.3) itibaren, içe aktarılan C yapılarının Swift'de varsayılan bir başlatıcısı vardır ve bu yapının tüm alanlarını sıfır olarak başlatır, böylece soket adres yapısı ile başlatılabilir.

    var zeroAddress = sockaddr_in()
  • sizeofValue()Bu yapının büyüklüğü, bu dönüştürülebilir zorundadır verir UInt8için sin_len:

    zeroAddress.sin_len = UInt8(sizeofValue(zeroAddress))
  • AF_INETbir Int32, bunun için doğru türe dönüştürülmesi gerekir sin_family:

    zeroAddress.sin_family = sa_family_t(AF_INET)
  • withUnsafePointer(&zeroAddress) { ... }yapının adresini argüman olarak kullanıldığı kapanışa iletir SCNetworkReachabilityCreateWithAddress(). UnsafePointer($0) Bu işlev için bir işaretçi beklediği için dönüşüm gerekli sockaddrdeğildir sockaddr_in.

  • Dönen değer withUnsafePointer(), dönüş değeridir SCNetworkReachabilityCreateWithAddress()ve türü vardır SCNetworkReachability?, yani isteğe bağlıdır. guard letBeyanı (Swift 2.0 yeni bir özelliktir) için Çizelgesi değerini atar defaultRouteReachabilityDeğilse değişken nil. Aksi takdirde elseblok yürütülür ve fonksiyon geri döner.

  • Swift 2'den itibaren, SCNetworkReachabilityCreateWithAddress()yönetilen bir nesne döndürür. Açıkça yayınlamanıza gerek yok.
  • Swift 2'den itibaren, set benzeri bir arayüze sahip SCNetworkReachabilityFlagsolana uygundur OptionSetType. İle boş bir bayrak değişkeni oluşturursunuz

    var flags : SCNetworkReachabilityFlags = []

    ve bayrakları kontrol edin

    let isReachable = flags.contains(.Reachable)
    let needsConnection = flags.contains(.ConnectionRequired)
  • İkinci parametresinin SCNetworkReachabilityGetFlagstürü vardır UnsafeMutablePointer<SCNetworkReachabilityFlags>, bu da bayrak değişkeninin adresini iletmeniz gerektiği anlamına gelir .

Notifier geri araması kaydetmenin Swift 2'den itibaren mümkün olduğunu unutmayın, Working with C APIs from Swift ve Swift 2 - UnsafeMutablePointer <Void> ile nesneyi karşılaştırın .


Swift 3/4 için güncelleme:

Güvenli olmayan işaretçiler artık farklı türde bir işaretçiye dönüştürülemez (bkz. - SE-0107 UnsafeRawPointer API ). İşte güncellenmiş kod:

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

4
@Isuru: UnsafePointer, bir C işaretçisinin Swift eşdeğeridir. argüman olarak adresiyle withUnsafePointer(&zeroAddress)aşağıdaki kapanışı çağırır . Kapanışın içinde, bu argüman duruyor. - Üzgünüm, hepsini birkaç cümleyle açıklamak imkansız. Swift kitabındaki kapanışlarla ilgili belgelere bir göz atın. $ 0 bir "steno bağımsız değişken adı" dır. { ...}zeroAddress$0
Martin R

1
@JAL: Haklısınız, Apple "Boolean" ın Swift ile nasıl eşleştirildiğini değiştirdi. Geri bildiriminiz için teşekkürler, cevabı buna göre güncelleyeceğim.
Martin R

1
Bu true, wifi bağlı değilse ve 4G açıksa ancak kullanıcı uygulamanın hücresel veriyi kullanamayacağını belirtmişse döner . Herhangi bir çözüm var mı?
Max Chuquimia

5
@Jugale: Şunun gibi bir şey yapabilirsiniz: let cellular = flags.contains(.IsWWAN) Boolean yerine bir çift döndürebilirsiniz, örneğin: func connectedToNetwork() -> (connected: Bool, cellular: Bool)
EdFunke

3
@Tejas: "Sıfır adres" yerine herhangi bir IP adresini kullanabilir veya dizge olarak bir ana bilgisayar adıyla SCNetworkReachabilityCreateWithName () kullanabilirsiniz. Ancak SCNetworkReachability'nin yalnızca bu adrese gönderilen bir paketin yerel aygıtı terk edip edemeyeceğini kontrol ettiğini unutmayın. Veri paketinin gerçekte ana bilgisayar tarafından alınacağını garanti etmez.
Martin R

12

Hızlı 3, IPv4, IPv6

Martin R'nin cevabına göre:

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

2
Benim için çalışmak ayrıca NET64 / IPV6 için de en iyi yol, unutmayınimport SystemConfiguration
Bhavin_m

@juanjo, kodunuzu kullanarak ulaşmak istediğiniz bir ana bilgisayarı nasıl ayarlarsınız
user2924482

6

Bunun Swift ile hiçbir ilgisi yoktur, ancak en iyi çözüm, ağın çevrimiçi olup olmadığını belirlemek için Erişilebilirliği KULLANMAMAKtır. Sadece bağlantınızı kurun ve başarısız olursa hataları halledin. Bir bağlantı kurmak, bazen uykuda olan çevrimdışı radyoları çalıştırabilir.

Erişilebilirliğin geçerli bir kullanımı, onu bir ağ çevrimdışından çevrimiçine geçiş yaptığında sizi bilgilendirmek için kullanmaktır. Bu noktada, başarısız olan bağlantıları yeniden denemelisiniz.



Hala adamcağız. Sadece bağlantıyı kurun ve hataları halledin. Bkz openradar.me/21581686 ve mail-archive.com/macnetworkprog@lists.apple.com/msg00200.html ve ilk açıklama burada mikeash.com/pyblog/friday-qa-2013-06-14-reachability.html
Erics

Bunu anlamadım - büyük bir yüklemeyi denemeden önce WiFi veya 3G'de olup olmadığınızı bilmek istemez miydiniz?
dumbledad

3
Tarihsel olarak, Ulaşılabilirlik, radyoların gücü kapalıysa çalışmıyordu. Bunu iOS 9'daki modern cihazlarda test etmedim, ancak iOS'un önceki sürümlerinde yalnızca bir bağlantı kurmanın iyi sonuç vermesine rağmen yükleme hatalarına neden olduğunu garanti ederim. Bir yüklemenin yalnızca WiFi üzerinden yapılmasını istiyorsanız, NSURLSessionAPI'yi ile birlikte kullanmalısınız NSURLSessionConfiguration.allowsCellularAccess = false.
EricS

3

En iyi çözüm , yazılan ReachabilitySwift sınıfıSwift 2 ve kullanımları kullanmaktır SCNetworkReachabilityRef.

Basit ve kolay:

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

Cazibe gibi çalışıyor.

Zevk almak


7
Herhangi bir üçüncü taraf bağımlılığını entegre etmeyi gerektirmediğinden kabul edilen yanıtı tercih ederim. Ek olarak, bu SCNetworkReachabilitySwift'de sınıfın nasıl kullanılacağı sorusuna cevap vermez , geçerli bir ağ bağlantısını kontrol etmek için kullanılacak bir bağımlılık önerisidir.
JAL

1

tekil örnek oluşturmak için juanjo'nun cevabını güncelledi

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

Kullanım

if Reachability.shared.isConnectedToNetwork(){

}

1

Bu Swift 4.0'da

Bu çerçeveyi kullanıyorum https://github.com/ashleymills/Reachability.swift
And Install Pod ..
In 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")
    }
}
}

ErişilebilirlikViewController ekranı internet yoksa görünecektir.


0

Swift 5, Kullanma 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))
    }
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.