質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.61%

  • Swift

    7022questions

    Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

  • iOS

    3900questions

    iOSとは、Apple製のスマートフォンであるiPhoneやタブレット端末のiPadに搭載しているオペレーションシステム(OS)です。その他にもiPod touch・Apple TVにも搭載されています。

  • JSON

    1114questions

    JSON(JavaScript Object Notation)は軽量なデータ記述言語の1つである。構文はJavaScriptをベースとしていますが、JavaScriptに限定されたものではなく、様々なソフトウェアやプログラミング言語間におけるデータの受け渡しが行えるように設計されています。

  • Ajax

    1064questions

    Ajaxとは、Webブラウザ内で搭載されているJavaScriptのHTTP通信機能を使って非同期通信を利用し、インターフェイスの構築などを行う技術の総称です。XMLドキュメントを指定したURLから読み込み、画面描画やユーザの操作などと並行してサーバと非同期に通信するWebアプリケーションを実現することができます。

  • WebSocket

    167questions

    WebSocketとは双方向・全二重コミュニケーションのためのAPIでありプロトコルのことを指します。WebSocketはHTML5に密接に結びついており、多くのウェブブラウザの最新版に導入されています。

SwiftでWebSocketイベント発火によるAPIコールについて

解決済

回答 2

投稿 編集

  • 評価
  • クリップ 1
  • VIEW 291

kazzzstudio

score 86

現在、チャットクライアントをSwiftで作っています。チャット相手のクライアントはJavascriptで実装済みです。

サーバー側は、Python(Django)で作っていて、クライアントからAPI経由で各種のリクエストを処理しています。
Swift側はAlamofireとSwiftyJSONでAPIを叩いています。

これまでは、すべてのAPIリクエストが正常に処理されていたのですが、チャット実装にともなって、相手から
チャットが更新されたイベントを受け取ったときに、更新されたチャット内容をロードするためにAPIを叩こうと
すると、処理が永久ループにハマってしまい、抜け出すことができずにいます。

こちらがチャットをロードするコードになります。

func reloadChatData() {
    let jsonData = loadChat(_registration_id: RData.getRegistrationId())

    if(jsonData["status"].string! == "success") {
        for chat in jsonData["chat"].arrayValue {
            if (chat["chat_from"] == "D") {
                let chat = ChatEntity(text: chat["chat"].string!, time: "", userType: .You)
                chats.append(chat)
            }
            else {
                let chat = ChatEntity(text: chat["chat"].string!, time: "", userType: .I)
                chats.append(chat)
            }
        }
    }
    tableView.reloadData()

}

func loadChat(_registration_id: String) -> JSON {
    let _commonURL = CommmonURL()
    var _load_chat_url = _commonURL.SERVER_HOSTNAME + _commonURL.LOAD_CHAT_ENTRY_POINT

    _load_chat_url = addParameter(url: _load_chat_url, named: "registration_id", value: _registration_id)
    let response = runQuery(_load_chat_url)
    let data: Data? = String(describing: response.rawValue).data(using: .utf8)
    let responseJson = JSON(data!) as JSON
    return responseJson
}

private func runQuery(_ _url: String) -> JSON {
    var responseData: JSON = JSON.null
    let urlString = _url
    var keepAlive = true

    Alamofire.request(urlString, method: .get, parameters: nil, encoding: JSONEncoding.default).responseJSON(completionHandler: { response in
        responseData = JSON(response.result.value!)
        keepAlive = false
        print("**")  // ********** When I call runQuery from websocket event listener function, the code don't reach this line.   **********
    })

    print(keepAlive)
    let runLoop = RunLoop.current
    while keepAlive &&
        runLoop.run(mode: RunLoopMode.defaultRunLoopMode, before: Date(timeIntervalSinceNow: 0.1)) {
    }

    return responseData
}

上記のreloadChatData()をViewControllerの任意の場所(WebSocketのイベント待受以外の場所)から実行すると、正常にチャットデータが返ってきます。
しかし、下記のコードからreloadChatData()を呼び出すと、runQuery()のkeepAliveがFalseにならずに永久ループにハマってしまいます。
サーバーのログでは、正常にクライアントにレスポンスを返しています。

override func viewDidLoad() {

    manager = SocketManager(socketURL: URL(string: SocketURL)!, config: [.log(true), .compress])
    socketIOClient = manager.defaultSocket

    socketIOClient.on(clientEvent: .connect) {data, ack in
        print(data)

        // Connect Event is fired...
        let data : Dictionary<String, String> = ["value": RData.getRegistrationId()]
        self.socketIOClient.emit("join_room", data)
        print("socket connected")
    }

    socketIOClient.on(clientEvent: .error) { (data, eck) in
        print(data)
        print("socket error")
    }

    socketIOClient.on(clientEvent: .disconnect) { (data, eck) in
        print(data)
        print("socket disconnect")
    }

    socketIOClient.on(clientEvent: SocketClientEvent.reconnect) { (data, eck) in
        print(data)
        print("socket reconnect")
    }

    socketIOClient.on("server_to_client") { data, emitter in
        print("Get Message from Another Client...")

        // ********** FAILED **********
        self.reloadChatData()
    }

    socketIOClient.connect()

}

相手(Javascriptクライアント)からチャットを送ると、Xcodeのコンソールには、
print("Get Message from Another Client...")
が出力されます。

WebSocketでデータを受信したときだけの問題なので、なんらかreloadChatData()関数を呼び出す方法に問題があると思うのですが、解決の糸口がつかめません。

どのように実装すればよいのかアドバイスをいただけないでしょうか?
よろしくお願いします。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 2

+2

WebSocket(というか、Socket.IO)の接続に使用されているのは、こちらのライブラリですね?

https://github.com/socketio/socket.io-client-swift

でしたら、クライアントのイベントコールバックはメインスレッドではなく、クライアント内部で用意されたスレッド上で呼び出されていたはずです。(しばらく触っていないので、もしかしたら仕様に変更があったかもしれません)

そうすると、ご質問のコードのrunQuery内で呼んでいる RunLoop.current は、メインスレッド以外のスレッドに対するRunLoopを返すことになります。それに対し、Alamofireの完了時コールバックはメインスレッドで呼ばれるので、RunLoop.currentを使っていくら待機させても処理が完了しないわけです。

一番簡単な解決方法は、メインスレッドでreloadChatDataを呼ぶようにしてあげることです。

socketIOClient.on("server_to_client") { [weak self] data, emitter in
    print("Get Message from Another Client...")

    // メインスレッドで実行
    DispatchQueue.main.async { [weak self] in
        self?.reloadChatData()
    }
}

ただ、通信処理をメインスレッドで待機させるコードは処理を止めることになってしまいそもそもあまり好ましくないので、待機させずコールバック内で処理をしていくように設計を見直した方がよいかと思います。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/05/06 22:54

    アドバイス、ありがとうございます!

    ご教示いただいたコードを試してみました。
    おそらくこのあたりの問題ではないかと思うのですが、まだループから抜け出せずにいます。

    止まっているループ側で、
    let runLoop = RunLoop.current

    let runLoop = RunLoop.main
    に変えてみても動きませんでした。

    キャンセル

  • 2018/05/08 11:12

    うーん、なるほど。。Alamofireの方もGCDのmain queueに完了時の処理を投げてると思うので、main queue内で待機処理をしてしまうとキューが詰まって完了時の処理が呼ばれなくなってしまうのかもしれません。

    代替案として、DispatchQueue.main.asyncの代わりにperformSelectorOnMainThreadを使ってみてはどうでしょうか?

    self?.performSelector(onMainThread: #selector(reloadChatData) ...)

    もっとも、RunLoopで同期待ち合わせっぽい処理をするのは本来の使い方ではなく挙動が保証されるものでもないので、使わない方向に変えていただくのがよさそうです。

    キャンセル

check解決した方法

0

解決しました。

Alamofireの呼び出し方法を変えたら、動作するようになりました。

    private func runQuery(_ _url: String) -> JSON {
        var responseData: JSON = JSON.null
        let urlString = _url
        var keepAlive = true

        let queue = DispatchQueue(label: "com.xxxxxxxx", qos: .background, attributes: .concurrent)

        Alamofire.request(urlString, method: .get, parameters: nil, encoding: JSONEncoding.default).responseJSON(queue: queue) { response in
            responseData = JSON(response.result.value!)
            keepAlive = false
        }

        let runLoop = RunLoop.current
        while keepAlive &&
            runLoop.run(mode: RunLoopMode.defaultRunLoopMode, before: Date(timeIntervalSinceNow: 0.1)) {
        }

        return responseData
    }

ご教示いただきまして、ありがとうございました。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 90.61%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る

  • Swift

    7022questions

    Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

  • iOS

    3900questions

    iOSとは、Apple製のスマートフォンであるiPhoneやタブレット端末のiPadに搭載しているオペレーションシステム(OS)です。その他にもiPod touch・Apple TVにも搭載されています。

  • JSON

    1114questions

    JSON(JavaScript Object Notation)は軽量なデータ記述言語の1つである。構文はJavaScriptをベースとしていますが、JavaScriptに限定されたものではなく、様々なソフトウェアやプログラミング言語間におけるデータの受け渡しが行えるように設計されています。

  • Ajax

    1064questions

    Ajaxとは、Webブラウザ内で搭載されているJavaScriptのHTTP通信機能を使って非同期通信を利用し、インターフェイスの構築などを行う技術の総称です。XMLドキュメントを指定したURLから読み込み、画面描画やユーザの操作などと並行してサーバと非同期に通信するWebアプリケーションを実現することができます。

  • WebSocket

    167questions

    WebSocketとは双方向・全二重コミュニケーションのためのAPIでありプロトコルのことを指します。WebSocketはHTML5に密接に結びついており、多くのウェブブラウザの最新版に導入されています。