วิธีใช้ SCNetworkReachability ใน Swift


99

ฉันกำลังพยายามแปลงข้อมูลโค้ดนี้เป็น 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)

เป็นที่คาดหวังผมได้รับบางข้อผิดพลาดอื่น ๆตัวแปรที่ใช้ภายในค่าเริ่มต้นของตัวเอง ฉันเข้าใจสาเหตุของข้อผิดพลาดนั้นด้วย ใน C พวกเขาประกาศอินสแตนซ์ก่อนจากนั้นจึงเติมพารามิเตอร์ มันเป็นไปไม่ได้ใน Swift เท่าที่ฉันรู้ ตอนนี้ฉันหลงทางจริงๆว่าจะทำอย่างไร

ฉันอ่านเอกสารอย่างเป็นทางการของ Apple เกี่ยวกับการโต้ตอบกับ C API ใน Swift แต่ไม่มีตัวอย่างในการทำงานกับโครงสร้าง

ใครก็ได้โปรดช่วยฉันที่นี่? ฉันจะขอบคุณจริงๆ

ขอบคุณ.


อัปเดต:ขอบคุณมาร์ตินฉันสามารถผ่านพ้นปัญหาแรกเริ่มได้ แต่ 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'ได้ คุณจะผ่านNULLSwift ได้อย่างไร?

ฉันเปลี่ยนบรรทัดนี้ด้วยและข้อผิดพลาดก็หายไปแล้ว

let didRetrieveFlags = SCNetworkReachabilityGetFlags(defaultRouteReachability, &flags)

แก้ไข 2:ฉันผ่านnilในสายนี้หลังจากที่ได้เห็นนี้คำถาม แต่ที่ตรงกันข้ามคำตอบที่มีคำตอบที่นี่ มันบอกว่าไม่มีทางเทียบเท่ากับNULLใน Swift

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

อย่างไรก็ตามฉันได้รับข้อผิดพลาดใหม่ว่า'sockaddr_in' ไม่เหมือนกับ 'sockaddr'ที่บรรทัดด้านบน


ฉันมีข้อผิดพลาดที่บรรทัดถ้า! ไม่สามารถใช้กับตัวถูกดำเนินการประเภทบูลีนได้ . . . กรุณาช่วย.
Zeebok

คำตอบ:


236

(คำตอบนี้ถูกขยายซ้ำ ๆ เนื่องจากการเปลี่ยนแปลงในภาษา 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) แปลงเป็นสิ่งจำเป็นเพราะฟังก์ชั่นที่คาดว่าจะเป็นตัวชี้ไป ไม่ได้sockaddrsockaddr_in

  • ค่าที่ส่งคืนจากwithUnsafePointer()คือค่าที่ส่งคืนจากSCNetworkReachabilityCreateWithAddress()และมีชนิดSCNetworkReachability?กล่าวคือเป็นทางเลือก guard letคำสั่ง (คุณลักษณะใหม่ในสวิฟท์ 2.0) กำหนดค่าที่ยังไม่ได้เปิดให้กับตัวแปรถ้ามันไม่ได้defaultRouteReachability nilมิฉะนั้นelseบล็อกจะถูกดำเนินการและฟังก์ชันจะกลับมา

  • เมื่อ Swift 2 SCNetworkReachabilityCreateWithAddress()ส่งคืนอ็อบเจ็กต์ที่มีการจัดการ คุณไม่จำเป็นต้องปล่อยอย่างชัดเจน
  • สำหรับ Swift 2 เป็นSCNetworkReachabilityFlagsไปตาม OptionSetTypeที่มีอินเทอร์เฟซที่เหมือนชุด คุณสร้างตัวแปรแฟล็กว่างด้วย

    var flags : SCNetworkReachabilityFlags = []

    และตรวจสอบแฟล็กด้วย

    let isReachable = flags.contains(.Reachable)
    let needsConnection = flags.contains(.ConnectionRequired)
    
  • พารามิเตอร์ที่สองของSCNetworkReachabilityGetFlagshas type UnsafeMutablePointer<SCNetworkReachabilityFlags>ซึ่งหมายความว่าคุณต้องส่งแอดเดรสของตัวแปรแฟล็ก

ยังทราบว่าการลงทะเบียนโทรกลับแจ้งเป็นไปได้ของสวิฟท์ 2 การเปรียบเทียบการทำงานกับ C APIs จากสวิฟท์และสวิฟท์ 2 - UnsafeMutablePointer <โมฆะ> ไปยังวัตถุ


อัปเดตสำหรับ Swift 3/4:

ตัวชี้ที่ไม่ปลอดภัยไม่สามารถแปลงเป็นตัวชี้ประเภทอื่นได้อีกต่อไป (ดู - SE-0107 UnsafeRawPointer API ) นี่คือรหัสที่อัปเดต:

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 คือ Swift เทียบเท่ากับตัวชี้ C withUnsafePointer(&zeroAddress)เรียกการปิดต่อไปนี้{ ...}ด้วยที่อยู่zeroAddressเป็นอาร์กิวเมนต์ ข้างในปิด$0หมายถึงอาร์กิวเมนต์นั้น - ขออภัยเป็นไปไม่ได้ที่จะอธิบายทั้งหมดนั้นในสองสามประโยค ดูเอกสารเกี่ยวกับการปิดในหนังสือ Swift $ 0 คือ "ชื่ออาร์กิวเมนต์ชวเลข"
Martin R

1
@JAL: คุณพูดถูก Apple เปลี่ยนวิธีการแมป "บูลีน" กับ Swift ขอบคุณสำหรับคำติชมเราจะอัปเดตคำตอบให้สอดคล้องกัน
Martin R

1
สิ่งนี้จะส่งคืนtrueหากไม่ได้เชื่อมต่อ wifi และ 4G เปิดอยู่ แต่ผู้ใช้ระบุว่าแอปไม่สามารถใช้ข้อมูลเซลลูลาร์ .. มีวิธีแก้ไขอย่างไร
Max Chuquimia

5
@Jugale: คุณสามารถทำสิ่งที่ชอบ: let cellular = flags.contains(.IsWWAN) คุณอาจส่งคืนสัมผัสแทนบูลีนเช่น: func connectedToNetwork() -> (connected: Bool, cellular: Bool)
EdFunke

3
@ Tejas: คุณสามารถใช้ที่อยู่ IP ใดก็ได้แทน "ที่อยู่ศูนย์" หรือใช้ SCNetworkReachabilityCreateWithName () โดยมีชื่อโฮสต์เป็นสตริง แต่โปรดทราบว่า SCNetworkReachability จะตรวจสอบว่าแพ็กเก็ตที่ส่งไปยังที่อยู่นั้นสามารถออกจากอุปกรณ์ภายในเครื่องได้ ไม่รับประกันว่าโฮสต์จะได้รับแพ็กเก็ตข้อมูลจริง
Martin R

12

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

2
การทำงานให้ฉันเป็นวิธีที่ดีที่สุดสำหรับ NET64 / IPV6 ด้วยอย่าลืมimport SystemConfiguration
Bhavin_m

@juanjo คุณตั้งค่าโฮสต์ที่ต้องการเข้าถึงโดยใช้รหัสของคุณอย่างไร
user2924482

6

สิ่งนี้ไม่เกี่ยวข้องกับ Swift แต่วิธีแก้ไขที่ดีที่สุดคืออย่าใช้ Reachability เพื่อตรวจสอบว่าเครือข่ายออนไลน์อยู่หรือไม่ เพียงทำการเชื่อมต่อและจัดการข้อผิดพลาดหากล้มเหลว บางครั้งการเชื่อมต่ออาจทำให้วิทยุออฟไลน์ที่อยู่เฉยๆดับลง

การใช้ Reachability ที่ถูกต้องวิธีหนึ่งคือการใช้เพื่อแจ้งให้คุณทราบเมื่อเครือข่ายเปลี่ยนจากออฟไลน์เป็นออนไลน์ เมื่อถึงจุดนั้นคุณควรลองการเชื่อมต่อที่ล้มเหลวอีกครั้ง


SCNetworkReachability ! = reachability
JAL

ยังคงมีรถ เพียงแค่ทำการเชื่อมต่อและจัดการข้อผิดพลาด ดูopenradar.me/21581686และmail-archive.com/macnetworkprog@lists.apple.com/msg00200.htmlและความคิดเห็นแรกที่นี่mikeash.com/pyblog/friday-qa-2013-06-14-reachability.html
EricS

ฉันไม่เข้าใจ - คุณไม่อยากรู้ว่าคุณใช้ WiFi หรือ 3G ก่อนที่จะพยายามอัปโหลดครั้งใหญ่หรือไม่?
dumbledad

3
ในอดีตความสามารถในการเข้าถึงไม่ทำงานหากวิทยุปิดอยู่ ฉันไม่ได้ทดสอบสิ่งนี้กับอุปกรณ์สมัยใหม่ใน iOS 9 แต่ฉันรับประกันได้ว่าเคยทำให้การอัปโหลดล้มเหลวใน iOS เวอร์ชันก่อนหน้าเมื่อเพียงแค่ทำการเชื่อมต่อก็จะทำงานได้ดี หากคุณต้องการอัปโหลดเพียงไปผ่านทางอินเตอร์เน็ตไร้สาย, คุณควรใช้NSURLSessionAPI NSURLSessionConfiguration.allowsCellularAccess = falseกับ
EricS

3

ทางออกที่ดีที่สุดคือการใช้งานReachabilitySwift ระดับเขียนและการใช้Swift 2SCNetworkReachabilityRef

ง่ายและสะดวก:

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

ทำงานอย่างมีเสน่ห์

สนุก


7
ฉันชอบคำตอบที่ได้รับการยอมรับเนื่องจากไม่จำเป็นต้องรวมการอ้างอิงของบุคคลที่สามใด ๆ นอกจากนี้สิ่งนี้ไม่ได้ตอบคำถามเกี่ยวกับวิธีใช้SCNetworkReachabilityคลาสใน Swift แต่เป็นคำแนะนำของการพึ่งพาเพื่อใช้ตรวจสอบการเชื่อมต่อเครือข่ายที่ถูกต้อง
JAL

1

อัปเดตคำตอบของ juanjo เพื่อสร้างอินสแตนซ์ซิงเกิลตัน

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

}

1

นี่คือใน 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 จะปรากฏขึ้นหากไม่มีอินเทอร์เน็ต


0

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))
    }
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.