Cara menggunakan SCNetworkReachability di Swift


99

Saya mencoba mengonversi potongan kode ini ke Swift. Saya berjuang untuk turun dari tanah karena beberapa kesulitan.

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

Masalah pertama dan utama yang saya alami adalah tentang cara mendefinisikan dan bekerja dengan struct C. Pada baris pertama ( struct sockaddr_in zeroAddress;) dari kode di atas, saya pikir mereka mendefinisikan sebuah instance yang dipanggil zeroAddressdari struct sockaddr_in (?), Saya asumsikan. Saya mencoba menyatakan varseperti ini.

var zeroAddress = sockaddr_in()

Tetapi saya mendapatkan kesalahan Argumen yang hilang untuk parameter 'sin_len' dalam panggilan yang dapat dimengerti karena struct itu mengambil sejumlah argumen. Jadi saya coba lagi.

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

Seperti yang diharapkan, saya mendapatkan beberapa Variabel kesalahan lain yang digunakan dalam nilai awalnya sendiri . Saya mengerti penyebab kesalahan itu juga. Di C, mereka mendeklarasikan instance terlebih dahulu dan kemudian mengisi parameternya. Ini tidak mungkin di Swift sejauh yang saya tahu. Jadi saya benar-benar bingung pada saat ini tentang apa yang harus dilakukan.

Saya membaca dokumen resmi Apple tentang berinteraksi dengan C API di Swift tetapi tidak memiliki contoh dalam bekerja dengan struct.

Adakah yang bisa membantu saya di sini? Saya sangat menghargainya.

Terima kasih.


PEMBARUAN: Berkat Martin saya bisa melewati masalah awal. Tapi tetap saja Swift tidak membuatnya lebih mudah bagiku. Saya mendapatkan beberapa kesalahan baru.

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: Oke saya mengubah baris ini menjadi ini,

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

Kesalahan baru yang saya dapatkan di baris ini adalah 'UnsafePointer' tidak dapat diubah menjadi 'CFAllocator' . Bagaimana cara Anda lulus NULLdi Swift?

Juga saya mengubah baris ini dan kesalahannya hilang sekarang.

let didRetrieveFlags = SCNetworkReachabilityGetFlags(defaultRouteReachability, &flags)

EDIT 2: Saya melewati nilbaris ini setelah melihat pertanyaan ini . Tetapi jawaban itu bertentangan dengan jawaban di sini . Dikatakan tidak ada yang setara dengan NULLdi Swift.

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

Bagaimanapun saya mendapatkan kesalahan baru yang mengatakan 'sockaddr_in' tidak identik dengan 'sockaddr' di baris di atas.


saya mengalami kesalahan pada baris jika! SCNetworkReachabilityGetFlags (defaultRouteReachability, & flags) yaitu operator unary! tidak dapat diterapkan ke operand tipe Boolean. . . . tolong bantu.
Zeebok

Jawaban:


236

(Jawaban ini diperpanjang berulang kali karena perubahan dalam bahasa Swift, yang membuatnya agak membingungkan. Sekarang saya telah menulis ulang dan menghapus semua yang merujuk ke Swift 1.x. Kode lama dapat ditemukan di riwayat edit jika ada yang membutuhkan Itu.)

Beginilah cara Anda melakukannya di 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)
}

Penjelasan:

  • Mulai dari Swift 1.2 (Xcode 6.3), struct C yang diimpor memiliki penginisialisasi default di Swift, yang menginisialisasi semua bidang struct ke nol, sehingga struktur alamat soket dapat diinisialisasi dengan

    var zeroAddress = sockaddr_in()
  • sizeofValue()memberikan ukuran struktur ini, ini harus diubah menjadi UInt8untuk sin_len:

    zeroAddress.sin_len = UInt8(sizeofValue(zeroAddress))
  • AF_INETadalah Int32, ini harus dikonversi ke jenis yang benar untuk sin_family:

    zeroAddress.sin_family = sa_family_t(AF_INET)
  • withUnsafePointer(&zeroAddress) { ... }meneruskan alamat struktur ke closure yang digunakan sebagai argumen untuk SCNetworkReachabilityCreateWithAddress(). The UnsafePointer($0) konversi diperlukan karena fungsi yang mengharapkan pointer ke sockaddr, tidak sockaddr_in.

  • Nilai yang dikembalikan dari withUnsafePointer()adalah nilai yang dikembalikan dari SCNetworkReachabilityCreateWithAddress()dan yang memiliki tipe SCNetworkReachability?, yaitu opsional. The guard letpernyataan (fitur baru di Swift 2.0) memberikan nilai yang terbuka ke defaultRouteReachabilityvariabel jika tidak nil. Jika tidak, elseblok akan dieksekusi dan fungsinya kembali.

  • Pada Swift 2, SCNetworkReachabilityCreateWithAddress()mengembalikan objek terkelola. Anda tidak harus merilisnya secara eksplisit.
  • Pada Swift 2, SCNetworkReachabilityFlagssesuai dengan OptionSetTypeyang memiliki antarmuka seperti set. Anda membuat variabel bendera kosong dengan

    var flags : SCNetworkReachabilityFlags = []

    dan periksa bendera dengan

    let isReachable = flags.contains(.Reachable)
    let needsConnection = flags.contains(.ConnectionRequired)
    
  • Parameter kedua SCNetworkReachabilityGetFlagsmemiliki tipe UnsafeMutablePointer<SCNetworkReachabilityFlags>, yang berarti Anda harus mengirimkan alamat variabel flags.

Perhatikan juga bahwa mendaftarkan callback notifier dapat dilakukan pada Swift 2, bandingkan Bekerja dengan C API dari Swift dan Swift 2 - UnsafeMutablePointer <Void> dengan objek .


Pembaruan untuk Swift 3/4:

Pointer yang tidak aman tidak dapat dengan mudah diubah menjadi pointer dari jenis yang berbeda lagi (lihat - API UnsafeRawPointer SE-0107 ). Berikut kode yang diperbarui:

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 adalah Swift yang setara dengan penunjuk C. withUnsafePointer(&zeroAddress)memanggil penutupan berikut { ...}dengan alamat zeroAddresssebagai argumen. Di dalam closure, $0singkatan dari argumen itu. - Maaf, tidak mungkin menjelaskan semua itu dalam beberapa kalimat. Lihat dokumentasi tentang penutupan di buku Swift. $ 0 adalah "nama argumen singkatan".
Martin R

1
@ JAL: Anda benar, Apple mengubah cara "Boolean" dipetakan ke Swift. Terima kasih atas tanggapan Anda, saya akan memperbarui jawaban yang sesuai.
Martin R

1
Ini mengembalikan truejika wifi tidak terhubung dan 4G aktif tetapi pengguna telah menentukan bahwa aplikasi tidak dapat menggunakan data seluler .. Ada solusi?
Max Chuquimia

5
@Jugale: Anda dapat melakukan sesuatu seperti: let cellular = flags.contains(.IsWWAN) Anda dapat mengembalikan touple alih-alih Boolean, seperti: func connectedToNetwork() -> (connected: Bool, cellular: Bool)
EdFunke

3
@Tejas: Anda dapat menggunakan alamat IP apa pun selain "alamat nol", atau menggunakan SCNetworkReachabilityCreateWithName () dengan nama host sebagai string. Tetapi perhatikan bahwa SCNetworkReachability hanya memeriksa bahwa paket yang dikirim ke alamat itu dapat meninggalkan perangkat lokal. Itu tidak menjamin bahwa paket data benar-benar akan diterima oleh tuan rumah.
Martin R

12

Swift 3, IPv4, IPv6

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

2
Bekerja untuk saya juga cara terbaik untuk NET64 / IPV6, jangan lupaimport SystemConfiguration
Bhavin_m

@juanjo, bagaimana Anda mengatur host yang ingin Anda jangkau menggunakan kode Anda
pengguna2924482

6

Ini tidak ada hubungannya dengan Swift, tetapi solusi terbaik adalah TIDAK menggunakan Reachability untuk menentukan apakah jaringan sedang online. Buat saja koneksi Anda dan tangani kesalahan jika gagal. Membuat koneksi terkadang dapat menyalakan radio offline yang tidak aktif.

Satu-satunya penggunaan Reachability yang valid adalah menggunakannya untuk memberi tahu Anda saat jaringan bertransisi dari offline ke online. Pada saat itu Anda harus mencoba kembali koneksi yang gagal.



Masih buggy. Buat saja koneksi dan tangani kesalahan. Lihat openradar.me/21581686 dan mail-archive.com/macnetworkprog@lists.apple.com/msg00200.html dan komentar pertama di sini mikeash.com/pyblog/friday-qa-2013-06-14-reachability.html
EricS

Saya tidak mengerti - tidakkah Anda ingin tahu apakah Anda menggunakan WiFi atau 3G sebelum mencoba mengunggah besar?
dumbledad

3
Secara historis, Reachability tidak berfungsi jika radio dimatikan. Saya belum menguji ini pada perangkat modern di iOS 9, tetapi saya jamin ini menyebabkan kegagalan unggahan di versi iOS sebelumnya ketika hanya membuat koneksi akan berfungsi dengan baik. Jika Anda ingin unggahan hanya melalui WiFi, Anda harus menggunakan NSURLSessionAPI dengan NSURLSessionConfiguration.allowsCellularAccess = false.
EricS

3

Solusi terbaik adalah dengan menggunakan ReachabilitySwift kelas , tertulis Swift 2, dan penggunaan SCNetworkReachabilityRef.

Sederhana dan mudah:

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

Bekerja seperti pesona.

Nikmati


7
Saya lebih suka jawaban yang diterima karena tidak memerlukan integrasi dependensi pihak ketiga. Selain itu, ini tidak menjawab pertanyaan tentang cara menggunakan SCNetworkReachabilitykelas di Swift, ini adalah saran ketergantungan yang digunakan untuk memeriksa koneksi jaringan yang valid.
JAL

1

memperbarui jawaban juanjo untuk membuat instance tunggal

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

Pemakaian

if Reachability.shared.isConnectedToNetwork(){

}

1

Ini ada di Swift 4.0

Saya menggunakan kerangka ini https://github.com/ashleymills/Reachability.swift
Dan Instal Pod ..
Di 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")
    }
}
}

Layar reachabilityViewController akan muncul jika internet tidak ada

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.