web-dev-qa-db-ja.com

SwiftでSCNetworkReachabilityを使用する方法

this コードスニペットを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;)で、それらはstruct sockaddr_in(?)からzeroAddressというインスタンスを定義していると思います。このようにvarを宣言してみました。

var zeroAddress = sockaddr_in()

しかし、エラーが発生します。callのパラメーター '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の公式 document SwiftのC APIとのやり取りについて読んでいますが、構造体を扱う例はありません。

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

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


UPDATE: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
}

EDIT 1:さて、この行をこれに変更しました、

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

この行で取得している新しいエラーは、「UnsafePointer」は「CFAllocator」に変換できません。 SwiftでNULLを渡す方法は?

また、この行を変更すると、エラーはなくなりました。

let didRetrieveFlags = SCNetworkReachabilityGetFlags(defaultRouteReachability, &flags)

EDIT 2:this の質問を見た後、この行にnilを渡しました。しかし、その答えは答えと矛盾します here 。 SwiftにはNULLに相当するものはないという。

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

とにかく、 'sockaddr_in'は上記の行の 'sockaddr'と同一ではないという新しいエラーが表示されます。

97
Isuru

(この答えは、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()はこの構造のサイズを示します。これは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_inではなくsockaddrへのポインターを予期しているためです。

  • withUnsafePointer()から返される値は、SCNetworkReachabilityCreateWithAddress()からの戻り値であり、タイプはSCNetworkReachability?です。つまり、オプションです。 guard letステートメント(Swift 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)
    
  • SCNetworkReachabilityGetFlagsの2番目のパラメーターのタイプはUnsafeMutablePointer<SCNetworkReachabilityFlags>です。つまり、flags変数のaddressを渡す必要があります。

また、Swift 2の時点でノーティファイアコールバックの登録が可能であることに注意してください。 SwiftのC APIの操作Swift 2-UnsafeMutablePointer <Void> to 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)
}
232
Martin R

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

これはSwiftとは関係ありませんが、最善の解決策は、ネットワークがオンラインかどうかを判断するためにReachabilityを使用しないことです。接続を確立し、失敗した場合はエラーを処理するだけです。接続を確立すると、休止状態のオフライン無線が起動することがあります。

Reachabilityの有効な使用法の1つは、ネットワークがオフラインからオンラインに移行したときに通知するために使用することです。その時点で、失敗した接続を再試行する必要があります。

6
EricS

最良の解決策は、Swift 2で記述されたReachabilitySwiftclass を使用し、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()

魅力のように働く。

楽しい

3
Bonnke

シングルトンインスタンスを作成するために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
anoop4real

これはSwift 4.0にあります

私はこのフレームワークを使用しています https://github.com/ashleymills/Reachability.Swift
そしてPodをインストールします..
InAppDelegate

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画面が表示されます

1
Sreekanth G