【Swift】NWConnectionの基本的な使い方をエンジニアが解説

Swiftには便利なフレームワークがたくさん用意されています。ここではネットワークフレームワークの1つである「NWConnection」の基本的な使い方について解説します。

「NWConnection」を使うことができれば、簡単にTCPやUDPといったプロトコルでネットワークコネクションが確立できるので、ぜひマスターしておきましょう。

目次

NWConnetionのコード例

まずは細かな説明の前に全体像を把握するため、コード例を載せておきます。(説明を簡単にするためエラー処理等を少し省いています)

import Foundation
import Network

class ExampleCode{
    func send(connection: NWConnection) {
        /* 送信データ生成 */
        let message = "example\n"
        let data = message.data(using: .utf8)!
        let semaphore = DispatchSemaphore(value: 0)

        /* データ送信 */
        connection.send(content: data, completion: .contentProcessed { error in
            if let error = error {
                NSLog("\(#function), \(error)")
            } else {
                semaphore.signal()
            }
        })
        /* 送信完了待ち */
        semaphore.wait()
    }
    
    func recv(connection: NWConnection) {
        let semaphore = DispatchSemaphore(value: 0)
        /* データ受信 */
        connection.receive(minimumIncompleteLength: 0,
                           maximumLength: 65535,
                           completion:{(data, context, flag, error) in
            if let error = error {
                NSLog("\(#function), \(error)")
            } else {
                if let data = data {
                    /* 受信データのデシリアライズ */
                    semaphore.signal()
                }
                else {
                    NSLog("receiveMessage data nil")
                }
            }
        })
        /* 受信完了待ち */
        semaphore.wait()
    }
    
    func disconnect(connection: NWConnection)
    {
        /* コネクション切断 */
        connection.cancel()
    }
    
    func connect(host: String, port: String) -> NWConnection
    {
        let t_host = NWEndpoint.Host(host)
        let t_port = NWEndpoint.Port(port)
        let connection : NWConnection
        let semaphore = DispatchSemaphore(value: 0)

        /* コネクションの初期化 */
        connection = NWConnection(host: t_host, port: t_port!, using: .tcp)

        /* コネクションのStateハンドラ設定 */
        connection.stateUpdateHandler = { (newState) in
            switch newState {
                case .ready:
                    NSLog("Ready to send")
                    semaphore.signal()
                case .waiting(let error):
                    NSLog("\(#function), \(error)")
                case .failed(let error):
                    NSLog("\(#function), \(error)")
                case .setup: break
                case .cancelled: break
                case .preparing: break
                @unknown default:
                    fatalError("Illegal state")
            }
        }
        
        /* コネクション開始 */
        let queue = DispatchQueue(label: "example")
        connection.start(queue:queue)

        /* コネクション完了待ち */
        semaphore.wait()
        return connection
    }
    
    func example()
    {
        let connection : NWConnection
        let host = "192.168.1.40"
        let port = "50000"
        
        /* コネクション開始 */
        connection = connect(host: host, port: port)
        /* データ送信 */
        send(connection: connection)
        /* データ受信 */
        recv(connection: connection)
        /* コネクション切断 */
        disconnect(connection: connection)
    }
}

ざっと見るとコード量がそこそこ有るように感じるかもしれませんが、やっていることはとても単純です。よく見るとNWConnectionが用意しているI/Fを順番に呼び出しているだけです。

これだけでデータを送受信できるんですから、これほどお手軽なことはないでしょう。

NWConnectionの代表的なI/F

ここからはコード例で使っていたI/Fの詳細な説明をします。これらを理解すると、上で紹介した全体のコード例がどれだけ簡単なものか、分かるはずです。

init()(コンストラクタ)

NWConnectionを使う上で一番最初の入り口となるI/Fでありコンストラクタです。ここで接続先のアドレスやポート、プロトコルを設定します。引数と戻り値は下記通り。

引数内容
host: NWEndpoint.Host接続先のアドレス or ホスト名
port: NWEndpoint.Port接続先のポート番号
using: NWParametersプロトコル設定値
戻り値内容
NWConnection生成されたNWConnectionのインスタンス
let connection : NWConnection
connection = NWConnection(host: NWEndpoint.Host("192.168.1.40"),
                          port: NWEndpoint.Port("50000"),
                          using: .tcp)

これでNWConnectionの初期化が完了です。

func start()

init()で設定した情報を元にコネクションを開始するI/Fです。引数と戻り値は下記通り。

引数内容
queue: DispatchQueueディスパッチキューオブジェクト。
NWConnectionの処理は基本的に非同期で行われるため、ディスパッチキューを渡す必要があります。

戻り値:なし

let queue = DispatchQueue(label: "example")
connection.start(queue:queue)

DispatchQueueには"example"という文字列を渡していますが、基本的に文字列なら何でもいいです。

func send()

確立したコネクションでデータ送信を行うI/Fです。引数と戻り値は下記通り。

引数内容
content: Data?送信データ
completion: NWConnection.SendCompletion送信完了時の処理

戻り値:なし

/* 送信データ生成 */
let message = "\n"
let data = message.data(using: .utf8)!
let semaphore = DispatchSemaphore(value: 0)

/* データ送信 */
connection.send(content: data, completion: .contentProcessed { error in
    if let error = error {
        NSLog("\(#function), \(error)")
    } else {
        semaphore.signal()
    }
})

/* 送信完了待ち */
semaphore.wait()

送信処理は非同期で行われます。sendを呼び出した後にセマフォを使って送信の完了待ちをしています。セマフォの解除は send の completion で行っており、送信処理が完了したら処理が再開されるといった感じです。

なお、コード例ではエラー時の処理がログ出力だけになっています。これだとエラー発生時に処理全体が止まってしまうので、ちゃんとした実装をするのであればエラー処理を行い、セマフォを解除してあげましょう。

func receive()

確立したコネクションでデータ受信を行うI/Fです。引数と戻り値は下記通り。

引数内容
minimumIncompleteLength: Int最小受信長
このサイズを超えるまで受信完了通知は来ません。
maximumLength: Int最大受信長
受信通知1回で受信できる長さ
completion受信完了時の処理

戻り値:なし

let semaphore = DispatchSemaphore(value: 0)
/* データ受信 */
connection.receive(minimumIncompleteLength: 0,
                    maximumLength: 65535,
                    completion:{(data, context, flag, error) in
    if let error = error {
        NSLog("\(#function), \(error)")
    } else {
        if let data = data {
            /* 受信データのデシリアライズ */
            semaphore.signal()
        }
        else {
            NSLog("receiveMessage data nil")
        }
    }
})
/* 受信完了待ち */
semaphore.wait()

受信処理も非同期で行われます。そのため、receiveを呼び出した後にセマフォを使って受信の完了待ちをしています。セマフォの解除は recieive の completion で行っており、受信処理が完了したら処理が再開されるといった感じです。

なお、コード例ではエラー時の処理がログ出力だけになっています。これだとエラー発生時に処理全体が止まってしまうので、ちゃんとした実装をするのであればエラー処理を行い、セマフォを解除してあげましょう。

func cancel()

確立したコネクションの切断を行うI/Fです。引数と戻り値はありません。

/* コネクション切断 */
connection.cancel()

説明をすることもないくらいシンプルです。

var stateUpdateHandler

これはコネクション状態が変化した場合の通知を受け取るハンドラになります。この変数に状態変化時の処理を入れておくことで、状態変化に合わせた処理を行うことができます。

/* コネクションのStateハンドラ設定 */
connection.stateUpdateHandler = { (newState) in
    switch newState {
        case .ready:
            NSLog("Ready to send")
            semaphore.signal()
        case .waiting(let error):
            NSLog("\(#function), \(error)")
        case .failed(let error):
            NSLog("\(#function), \(error)")
        case .setup: break
        case .cancelled: break
        case .preparing: break
        @unknown default:
            fatalError("Illegal state")
    }
}

全体のコード例ではコネクションがReady状態(送受信が可能な状態)になったことを検知するために、上記のような処理を "stateUpdateHandler" に入れていました。何の状態に変化したかは "newState" を見ることで確認することができるので、"newState" の状態によって処理を切り替えています。

例によって、コード例ではエラー時の処理がログ出力だけになっています。これだとエラー発生時に処理全体が止まってしまうので、ちゃんとした実装をするのであればエラー処理を行い、セマフォを解除するなりして、本線の処理を再開してあげましょう。

ちなみに、この "stateUpdateHandler" を使わなくても、コネクションの状態は "var state: NWConnection.State" を見ることで確認できます。そのため、Ready状態になったかどうかは、下記のように自身でポーリングしても確認できますよ。

for _: Int32 in 0..<100000 {
    if .ready == g_connection.state {
        break;
    }
}

まあ、基本的には "stateUpdateHandler" を使って、状態変化に合わせて処理した方が良いと思いますが、何かしら状態を見たい場面が出てきたら、"var state: NWConnection.State" を使うと良いでしょう。

NWConnectionの基本的なシーケンス

NWConnectionを使う流れとしては、大体こんな感じになります。

  1. "NWConnection.init()" でNWConnectionのインスタンス生成
  2. "NWConnection.start()" でコネクション開始
  3. "NWConnection.state" がReady状態になるまで待つ
  4. "NWConnection.send() or .receive()" でデータの送受信を行う
  5. "NWConnection.cancel()" でコネクション切断

最初に見たコードは、実はこれを行っているだけというのが見返してみると分かると思います。

まとめ

NWConnectionはとても便利なネットワークフレームワークです。これが使えれば簡単にネットワークを使ったアプリなどを作ることができます。ココで紹介した基本部分だけでもしっかりと抑えておきましょうね。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

目次