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;上記のコードの最初の行()ではzeroAddress、struct sockaddr_in(?)から呼び出されるインスタンスを定義していると思います。varこのような宣言をしてみました。

var zeroAddress = sockaddr_in()

しかし、構造体がいくつかの引数をとるので理解できる呼び出しパラメータ 'sin_len'のMissing argumentというエラーが表示されます。だからもう一度やり直した。

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の公式ドキュメントを読みましたが、構造体の使用例はありません。

誰か私をここで助けてくれますか?本当にありがたいです。

ありがとうございました。


更新:マーティンのおかげで、最初の問題を回避することができました。しかし、それでも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)、つまり単項演算子の場合、行にエラーがあります!ブール型のオペランドには適用できません。。。。助けてください。
Zeebok

回答:


236

(この回答は、Swift言語の変更により繰り返し拡張されたため、少し混乱を招きました。Swift1.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_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準拠し OptionSetTypeています。空のフラグ変数を作成するには

    var flags : SCNetworkReachabilityFlags = []

    とフラグをチェック

    let isReachable = flags.contains(.Reachable)
    let needsConnection = flags.contains(.ConnectionRequired)
  • の2番目のパラメーターのSCNetworkReachabilityGetFlagsタイプ UnsafeMutablePointer<SCNetworkReachabilityFlags>はです。つまり、フラグ変数のアドレスを渡す必要があります。

通知コールバックの登録はSwift 2以降で可能であることにも注意してください。SwiftおよびSwift 2のC APIの使用-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は「ブール」がSwiftにマッピングされる方法を変更しました。あなたのフィードバックをありがとう、私はそれに応じて答えを更新します。
マーティンR

1
これはtrue、WiFiが接続されておらず、4Gがオンになっているが、ユーザーがアプリがモバイルデータを使用しないことを指定した場合に返されます。
マックスチュキミア2015

5
@Jugale:次のようなことができます:次のようなlet cellular = flags.contains(.IsWWAN) ブール値の代わりにtoupleを返すことができます : func connectedToNetwork() -> (connected: Bool, cellular: Bool)
EdFunke

3
@Tejas:「ゼロアドレス」の代わりに任意のIPアドレスを使用するか、ホスト名を文字列としてSCNetworkReachabilityCreateWithName()を使用できます。ただし、SCNetworkReachabilityは、そのアドレスに送信されたパケットがローカルデバイスを離れることができることのみを確認することに注意してください。データパケットが実際にホストによって受信されることは保証されません。
マーティンR

12

Swift 3、IPv4、IPv6

マーティン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の有効な用途の1つは、ネットワークがオフラインからオンラインに移行したときに通知することです。その時点で、失敗した接続を再試行する必要があります。



まだバギー。接続してエラーを処理するだけです。参照openradar.me/21581686mail-archive.com/macnetworkprog@lists.apple.com/msg00200.html、ここで最初のコメントmikeash.com/pyblog/friday-qa-2013-06-14-reachability.html
EricSを

これはわかりません-大きなアップロードを試みる前に、WiFiと3Gのどちらを使用していたのか知​​りたくありませんか?
dumbledad 2016年

3
歴史的に、無線機の電源が切られている場合、到達可能性は機能しませんでした。私はこれをiOS 9の最新のデバイスでテストしていませんが、以前のバージョンのiOSで接続を確立するだけで問題がなかった場合に、アップロードの失敗を引き起こすことを保証しました。アップロードをWiFi経由でのみ行う場合は、でNSURLSessionAPIを使用する必要がありますNSURLSessionConfiguration.allowsCellularAccess = false
EricS 2016年

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を
ポッドをインストールします。..
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.