Cách sử dụng SCNetworkReachability trong Swift


99

Tôi đang cố gắng để chuyển đổi này đoạn mã để Swift. Tôi đang vật lộn để bắt đầu vì một số khó khăn.

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

Vấn đề đầu tiên và chính mà tôi gặp phải là về cách xác định và làm việc với C cấu trúc. Trong dòng đầu tiên ( struct sockaddr_in zeroAddress;) của đoạn mã trên, tôi nghĩ rằng họ đang xác định một phiên bản được gọi zeroAddresstừ struct sockaddr_in (?), Tôi giả sử như vậy. Tôi đã thử khai báo varnhư thế này.

var zeroAddress = sockaddr_in()

Nhưng tôi gặp lỗi Thiếu đối số cho tham số 'sin_len' trong cuộc gọi , điều này có thể hiểu được vì cấu trúc đó có một số đối số. Vì vậy, tôi đã thử lại.

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

Như mong đợi, tôi gặp một số lỗi khác Biến được sử dụng trong giá trị ban đầu của chính nó . Tôi cũng hiểu nguyên nhân của lỗi đó. Trong C, chúng khai báo instance trước rồi điền các tham số. Nó không khả thi trong Swift theo như tôi biết. Vì vậy, vào thời điểm này tôi thực sự không biết phải làm gì.

Tôi đã đọc tài liệu chính thức của Apple về cách tương tác với C API trong Swift nhưng không có ví dụ nào về cách làm việc với cấu trúc.

Bất cứ ai có thể vui lòng giúp tôi ra đây? Tôi thực sự đánh giá cao nó.

Cảm ơn bạn.


CẬP NHẬT: Nhờ Martin, tôi đã có thể vượt qua vấn đề ban đầu. Nhưng Swift vẫn không làm cho tôi dễ dàng hơn. Tôi nhận được nhiều lỗi mới.

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
}

CHỈNH SỬA 1: Được rồi, tôi đã thay đổi dòng này thành dòng này,

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

Lỗi mới mà tôi gặp phải ở dòng này là 'UnsafePointer' không thể chuyển đổi thành 'CFAllocator' . Làm thế nào để bạn vượt qua NULLtrong Swift?

Ngoài ra, tôi đã thay đổi dòng này và lỗi đã biến mất.

let didRetrieveFlags = SCNetworkReachabilityGetFlags(defaultRouteReachability, &flags)

CHỈNH SỬA 2: Tôi đã vượt qua nildòng này sau khi nhìn thấy câu hỏi này . Nhưng câu trả lời đó mâu thuẫn với câu trả lời ở đây . Nó nói rằng không có tương đương với NULLtrong Swift.

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

Dù sao, tôi nhận được một lỗi mới nói rằng 'sockaddr_in' không giống với 'sockaddr' ở dòng trên.


tôi đang gặp lỗi tại dòng if! SCNetworkReachabilityGetFlags (defaultRouteReachability, & flags) tức là toán tử một ngôi! không thể áp dụng cho toán hạng kiểu Boolean. . . . xin vui lòng giúp đỡ.
Zeebok

Câu trả lời:


236

(Câu trả lời này đã được mở rộng liên tục do những thay đổi trong ngôn ngữ Swift, điều này khiến nó hơi khó hiểu. Hiện tôi đã viết lại nó và xóa mọi thứ liên quan đến Swift 1.x. Bạn có thể tìm thấy mã cũ hơn trong lịch sử chỉnh sửa nếu ai đó cần nó.)

Đây là cách bạn làm trong 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)
}

Giải thích:

  • Kể từ Swift 1.2 (Xcode 6.3), cấu trúc C được nhập có bộ khởi tạo mặc định trong Swift, khởi tạo tất cả các trường của cấu trúc bằng 0, vì vậy cấu trúc địa chỉ socket có thể được khởi tạo bằng

    var zeroAddress = sockaddr_in()
  • sizeofValue()cho kích thước của cấu trúc này, điều này đã được chuyển đổi sang UInt8cho sin_len:

    zeroAddress.sin_len = UInt8(sizeofValue(zeroAddress))
  • AF_INETlà một Int32, điều này phải được chuyển đổi thành loại chính xác cho sin_family:

    zeroAddress.sin_family = sa_family_t(AF_INET)
  • withUnsafePointer(&zeroAddress) { ... }chuyển địa chỉ của cấu trúc đến bao đóng nơi nó được sử dụng làm đối số SCNetworkReachabilityCreateWithAddress(). Việc UnsafePointer($0) chuyển đổi là cần thiết vì hàm đó mong đợi một con trỏ đến sockaddr, không phải sockaddr_in.

  • Giá trị trả về từ withUnsafePointer()là giá trị trả về từ SCNetworkReachabilityCreateWithAddress()và có kiểu SCNetworkReachability?, tức là nó là tùy chọn. Câu guard letlệnh (một tính năng mới trong Swift 2.0) chỉ định giá trị chưa được bao bọc cho defaultRouteReachabilitybiến nếu nó không phải nil. Nếu không, elsekhối được thực thi và hàm trả về.

  • Kể từ Swift 2, SCNetworkReachabilityCreateWithAddress()trả về một đối tượng được quản lý. Bạn không cần phải phát hành nó một cách rõ ràng.
  • Đối với Swift 2, SCNetworkReachabilityFlagstuân theo OptionSetTypenó có giao diện giống như một bộ. Bạn tạo một biến cờ trống với

    var flags : SCNetworkReachabilityFlags = []

    và kiểm tra cờ với

    let isReachable = flags.contains(.Reachable)
    let needsConnection = flags.contains(.ConnectionRequired)
  • Tham số thứ hai của SCNetworkReachabilityGetFlagscó kiểu UnsafeMutablePointer<SCNetworkReachabilityFlags>, có nghĩa là bạn phải chuyển địa chỉ của biến cờ.

Cũng lưu ý rằng việc đăng ký gọi lại trình thông báo có thể thực hiện được với Swift 2, hãy so sánh Làm việc với API C từ SwiftSwift 2 - UnsafeMutablePointer <Void> với đối tượng .


Cập nhật cho Swift 3/4:

Không thể đơn giản chuyển đổi con trỏ không an toàn thành một con trỏ thuộc loại khác nữa (xem - SE-0107 UnsafeRawPointer API ). Đây là mã được cập nhật:

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 là Swift tương đương với con trỏ C. withUnsafePointer(&zeroAddress)gọi bao đóng sau { ...}với địa chỉ zeroAddresslà đối số. Bên trong đóng, $0là viết tắt của đối số đó. - Xin lỗi, không thể giải thích tất cả những điều đó trong một vài câu. Hãy xem tài liệu về các bao đóng trong sách Swift. $ 0 là "tên đối số viết tắt".
Martin R

1
@JAL: Bạn nói đúng, Apple đã thay đổi cách ánh xạ "Boolean" sang Swift. Cảm ơn phản hồi của bạn, tôi sẽ cập nhật câu trả lời cho phù hợp.
Martin R

1
Điều này trả về truenếu bạn không kết nối wifi và bật 4G nhưng người dùng đã chỉ định rằng ứng dụng không được sử dụng dữ liệu di động. Có giải pháp nào không?
Max Chuquimia

5
@Jugale: Bạn có thể làm một cái gì đó như: let cellular = flags.contains(.IsWWAN) Bạn có thể trả về một touple thay vì một Boolean, như: func connectedToNetwork() -> (connected: Bool, cellular: Bool)
EdFunke

3
@Tejas: Bạn có thể sử dụng bất kỳ địa chỉ IP nào thay vì "địa chỉ 0" hoặc sử dụng SCNetworkReachabilityCreateWithName () với tên máy chủ là chuỗi. Nhưng lưu ý rằng SCNetworkReachability chỉ kiểm tra xem một gói được gửi đến địa chỉ đó có thể rời khỏi thiết bị cục bộ hay không. Nó không đảm bảo rằng máy chủ thực sự sẽ nhận được gói dữ liệu.
Martin R

12

Swift 3, IPv4, IPv6

Dựa trên câu trả lời của 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
Làm việc đối với tôi cũng là cách tốt nhất cho NET64 / IPV6 cũng có, đừng quênimport SystemConfiguration
Bhavin_m

@juanjo, làm cách nào để bạn đặt máy chủ lưu trữ mà bạn muốn tiếp cận bằng mã của mình
user2924482 23/12/19

6

Điều này không liên quan gì đến Swift, nhưng giải pháp tốt nhất là KHÔNG sử dụng Khả năng tiếp cận để xác định xem mạng có trực tuyến hay không. Chỉ cần tạo kết nối của bạn và xử lý lỗi nếu nó không thành công. Việc tạo kết nối đôi khi có thể kích hoạt các radio ngoại tuyến không hoạt động.

Một cách sử dụng hợp lệ của Khả năng tiếp cận là sử dụng nó để thông báo cho bạn khi mạng chuyển đổi từ ngoại tuyến sang trực tuyến. Tại thời điểm đó, bạn nên thử lại các kết nối không thành công.



Vẫn còn lỗi. Chỉ cần thực hiện kết nối và xử lý lỗi. Xem openradar.me/21581686 mail-archive.com/macnetworkprog@lists.apple.com/msg00200.html và nhận xét đầu tiên tại đây mikeash.com/pyblog/friday-qa-2013-06-14-reachability.html
EricS

Tôi không hiểu điều này - bạn sẽ không muốn biết mình đang sử dụng WiFi hay 3G trước khi cố gắng tải lên lớn?
câm

3
Trước đây, Khả năng tiếp cận không hoạt động nếu bộ đàm bị tắt nguồn. Tôi chưa thử nghiệm điều này trên các thiết bị hiện đại trong iOS 9, nhưng tôi đảm bảo rằng nó đã từng gây ra lỗi tải lên trong các phiên bản iOS trước đó khi chỉ cần tạo kết nối sẽ hoạt động tốt. Nếu bạn muốn tải lên chỉ qua WiFi, bạn nên sử dụng NSURLSessionAPI với NSURLSessionConfiguration.allowsCellularAccess = false.
EricS

3

Giải pháp tốt nhất là sử dụng ReachabilitySwift lớp , được viết trong Swift 2và sử dụng SCNetworkReachabilityRef.

Đơn giản và dễ dàng:

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

Làm việc như một cái duyên.

Thưởng thức


7
Tôi thích câu trả lời được chấp nhận hơn vì nó không yêu cầu tích hợp bất kỳ phụ thuộc nào của bên thứ ba. Ngoài ra, điều này không trả lời câu hỏi về cách sử dụng SCNetworkReachabilitylớp trong Swift, nó là một gợi ý về sự phụ thuộc để sử dụng để kiểm tra kết nối mạng hợp lệ.
JAL

1

đã cập nhật câu trả lời của juanjo để tạo cá thể 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)
            }
        })
    }
}

Sử dụng

if Reachability.shared.isConnectedToNetwork(){

}

1

Đây là trong Swift 4.0

Tôi đang sử dụng khung này https://github.com/ashleymills/Reachability.swift
And Install Pod ..
Trong 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")
    }
}
}

Màn hình ReahabilityViewController sẽ xuất hiện nếu không có Internet

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.