Swift에서 SCNetworkReachability를 사용하는 방법


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;위 코드 의 첫 번째 줄 ( )에서 나는 그들이 zeroAddresssockaddr_in (?) 구조체에서 호출 된 인스턴스를 정의하고 있다고 생각합니다. 나는 var이와 같은 선언을 시도 했습니다.

var zeroAddress = sockaddr_in()

그러나 해당 구조체가 여러 인수를 사용하기 때문에 이해할 수 있는 호출에서 매개 변수 'sin_len'에 대한 Missing 인수 오류가 발생 합니다. 그래서 다시 시도했습니다.

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

예상대로 자체 초기 값 내에서 사용되는 다른 오류 변수가 있습니다. . 그 오류의 원인도 이해합니다. C에서는 먼저 인스턴스를 선언 한 다음 매개 변수를 채 웁니다. 내가 아는 한 Swift에서는 불가능합니다. 그래서 저는이 시점에서 무엇을해야할지 정말로 길을 잃었습니다.

Swift에서 C API와의 상호 작용에 대한 Apple의 공식 문서 를 읽었 지만 구조체 작업에 대한 예제가 없습니다.

누구든지 여기서 나를 도울 수 있습니까? 정말 감사하겠습니다.

감사합니다.


업데이트 : Martin 덕분에 초기 문제를 극복 할 수있었습니다. 그러나 여전히 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을 본 후이 줄을 통과 했습니다 . 그러나 그 대답은 여기 의 대답과 모순 됩니다 . 그것은 Swift에 상응하는 것이 없다고 말합니다 .NULL

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

어쨌든 위 줄에서 'sockaddr_in'이 'sockaddr'과 동일하지 않다는 새로운 오류가 발생 합니다.


! SCNetworkReachabilityGetFlags (defaultRouteReachability, & flags) ie 단항 연산자! 부울 유형의 피연산자에는 적용 할 수 없습니다. . . . 도와주세요.
Zeebok 2016-04-18

답변:


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의 기본 초기화 프로그램이 있습니다.이 초기화는 구조체의 모든 필드를 0으로 초기화하므로 소켓 주소 구조를 다음과 같이 초기화 할 수 있습니다.

    var zeroAddress = sockaddr_in()
  • sizeofValue()이러한 구조의 크기는,이 변환되어야 제공 UInt8sin_len:

    zeroAddress.sin_len = UInt8(sizeofValue(zeroAddress))
  • AF_INETInt32이면 올바른 유형으로 변환해야합니다 sin_family.

    zeroAddress.sin_family = sa_family_t(AF_INET)
  • withUnsafePointer(&zeroAddress) { ... }구조의 주소를에 대한 인수로 사용되는 클로저에 전달합니다 SCNetworkReachabilityCreateWithAddress(). 이 UnsafePointer($0) 함수는에 대한 포인터가 sockaddr아니라에 대한 포인터를 예상하기 때문에 변환이 필요합니다 sockaddr_in.

  • 에서 반환 된 값 withUnsafePointer()의 리턴 값 SCNetworkReachabilityCreateWithAddress()과 그 유형이있다 SCNetworkReachability?즉 그것이 선택 사항입니다. guard let문 (스위프트 2.0의 새로운 기능)은 상기 언 래핑 된 값에 할당 defaultRouteReachability그렇지 않은 경우 변수를 nil. 그렇지 않으면 else블록이 실행되고 함수가 반환됩니다.

  • Swift 2부터는 SCNetworkReachabilityCreateWithAddress()관리되는 객체를 반환합니다. 명시 적으로 해제 할 필요는 없습니다.
  • Swift 2 SCNetworkReachabilityFlags부터는 OptionSetTypeset-like 인터페이스를 가지고 있습니다. 다음을 사용하여 빈 플래그 변수를 만듭니다.

    var flags : SCNetworkReachabilityFlags = []

    플래그를 확인하십시오.

    let isReachable = flags.contains(.Reachable)
    let needsConnection = flags.contains(.ConnectionRequired)
  • 의 두 번째 매개 변수 SCNetworkReachabilityGetFlags에는 유형이 있습니다 UnsafeMutablePointer<SCNetworkReachabilityFlags>. 이는 flags 변수 의 주소 를 전달해야 함을 의미 합니다.

알림 콜백 등록은 Swift 2부터 가능합니다. Working with C APIs from SwiftSwift 2-UnsafeMutablePointer <Void>를 object와 비교하십시오 .


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는 C 포인터에 해당하는 Swift입니다. 의 주소 를 인수로 사용 withUnsafePointer(&zeroAddress)하여 다음 클로저 { ...}를 호출합니다 zeroAddress. 클로저 안에는 $0그 주장을 의미합니다. -죄송합니다. 몇 문장으로 설명하는 것은 불가능합니다. Swift 책에서 클로저에 대한 문서를보십시오. $ 0은 "축약 형 인수 이름"입니다.
Martin R

1
@JAL : 맞습니다. Apple은 "Boolean"이 Swift에 매핑되는 방식을 변경했습니다. 의견을 보내 주셔서 감사합니다. 그에 따라 답변을 업데이트하겠습니다.
Martin R

1
trueWi-Fi가 연결되어 있지 않고 4G가 켜져 있지만 사용자가 앱이 셀룰러 데이터를 사용하지 않을 수 있다고 지정한 경우에 반환 됩니다. 어떤 해결책이 있습니까?
최대 Chuquimia

5
@Jugale : 당신은 뭔가를 같이 할 수있는 : let cellular = flags.contains(.IsWWAN) 당신은 같은 부울 대신 touple를 반환 할 수 있습니다 func connectedToNetwork() -> (connected: Bool, cellular: Bool)
EdFunke

3
@Tejas : "0 주소"대신 임의의 IP 주소를 사용하거나 호스트 이름을 문자열로 사용하여 SCNetworkReachabilityCreateWithName ()을 사용할 수 있습니다. 그러나 SCNetworkReachability는 해당 주소로 전송 된 패킷이 로컬 장치를 떠날 수 있는지 만 확인합니다. 데이터 패킷이 호스트에서 실제로 수신된다는 보장은 없습니다.
Martin R

12

스위프트 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의 한 가지 유효한 용도는 네트워크가 오프라인에서 온라인으로 전환 될 때이를 알리는 데 사용하는 것입니다. 이 시점에서 실패한 연결을 다시 시도해야합니다.



여전히 버그가 있습니다. 연결하고 오류를 처리하기 만하면됩니다. 참조 openradar.me/21581686mail-archive.com/macnetworkprog@lists.apple.com/msg00200.html 첫 번째 주석 여기 mikeash.com/pyblog/friday-qa-2013-06-14-reachability.html
EricS을

이해가 안 돼요-대용량 업로드를 시도하기 전에 Wi-Fi 또는 3G에 있는지 알고 싶지 않나요?
dumbledad

3
역사적으로 도달 가능성은 라디오의 전원이 꺼지면 작동하지 않았습니다. iOS 9의 최신 장치에서 이것을 테스트하지는 않았지만 단순히 연결 만하면 정상적으로 작동했을 때 이전 버전의 iOS에서 업로드 실패를 유발했음을 보증합니다. 당신이 업로드는 WiFi를 통해 가고 싶은 경우에, 당신은 사용해야 NSURLSession와 API를 NSURLSessionConfiguration.allowsCellularAccess = false.
EricS

3

가장 좋은 방법은 사용하는 것입니다 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()

매력처럼 작동합니다.

즐겨


7
타사 종속성을 통합 할 필요가 없기 때문에 허용되는 답변을 선호합니다. 또한 이것은 SCNetworkReachabilitySwift에서 클래스 를 사용하는 방법에 대한 질문에 대답하지 않으며 유효한 네트워크 연결을 확인하는 데 사용할 종속성을 제안합니다.
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
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")
    }
}
}

인터넷이 없으면 reachabilityViewController 화면이 나타납니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.