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

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

新規登録して質問してみよう
ただいま回答率
85.48%
JSON

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

iOS

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

WebSocket

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

Swift

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

Ajax

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

Q&A

解決済

2回答

4175閲覧

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

kazzzstudio

総合スコア94

JSON

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

iOS

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

WebSocket

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

Swift

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

Ajax

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

0グッド

1クリップ

投稿2018/05/05 13:07

編集2018/05/05 13:10

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

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

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

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

Swift

1func reloadChatData() { 2 let jsonData = loadChat(_registration_id: RData.getRegistrationId()) 3 4 if(jsonData["status"].string! == "success") { 5 for chat in jsonData["chat"].arrayValue { 6 if (chat["chat_from"] == "D") { 7 let chat = ChatEntity(text: chat["chat"].string!, time: "", userType: .You) 8 chats.append(chat) 9 } 10 else { 11 let chat = ChatEntity(text: chat["chat"].string!, time: "", userType: .I) 12 chats.append(chat) 13 } 14 } 15 } 16 tableView.reloadData() 17 18} 19 20func loadChat(_registration_id: String) -> JSON { 21 let _commonURL = CommmonURL() 22 var _load_chat_url = _commonURL.SERVER_HOSTNAME + _commonURL.LOAD_CHAT_ENTRY_POINT 23 24 _load_chat_url = addParameter(url: _load_chat_url, named: "registration_id", value: _registration_id) 25 let response = runQuery(_load_chat_url) 26 let data: Data? = String(describing: response.rawValue).data(using: .utf8) 27 let responseJson = JSON(data!) as JSON 28 return responseJson 29} 30 31private func runQuery(_ _url: String) -> JSON { 32 var responseData: JSON = JSON.null 33 let urlString = _url 34 var keepAlive = true 35 36 Alamofire.request(urlString, method: .get, parameters: nil, encoding: JSONEncoding.default).responseJSON(completionHandler: { response in 37 responseData = JSON(response.result.value!) 38 keepAlive = false 39 print("**") // ********** When I call runQuery from websocket event listener function, the code don't reach this line. ********** 40 }) 41 42 print(keepAlive) 43 let runLoop = RunLoop.current 44 while keepAlive && 45 runLoop.run(mode: RunLoopMode.defaultRunLoopMode, before: Date(timeIntervalSinceNow: 0.1)) { 46 } 47 48 return responseData 49}

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

Swift

1override func viewDidLoad() { 2 3 manager = SocketManager(socketURL: URL(string: SocketURL)!, config: [.log(true), .compress]) 4 socketIOClient = manager.defaultSocket 5 6 socketIOClient.on(clientEvent: .connect) {data, ack in 7 print(data) 8 9 // Connect Event is fired... 10 let data : Dictionary<String, String> = ["value": RData.getRegistrationId()] 11 self.socketIOClient.emit("join_room", data) 12 print("socket connected") 13 } 14 15 socketIOClient.on(clientEvent: .error) { (data, eck) in 16 print(data) 17 print("socket error") 18 } 19 20 socketIOClient.on(clientEvent: .disconnect) { (data, eck) in 21 print(data) 22 print("socket disconnect") 23 } 24 25 socketIOClient.on(clientEvent: SocketClientEvent.reconnect) { (data, eck) in 26 print(data) 27 print("socket reconnect") 28 } 29 30 socketIOClient.on("server_to_client") { data, emitter in 31 print("Get Message from Another Client...") 32 33 // ********** FAILED ********** 34 self.reloadChatData() 35 } 36 37 socketIOClient.connect() 38 39}

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

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

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

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

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

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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

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

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

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

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

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

swift

1socketIOClient.on("server_to_client") { [weak self] data, emitter in 2 print("Get Message from Another Client...") 3 4 // メインスレッドで実行 5 DispatchQueue.main.async { [weak self] in 6 self?.reloadChatData() 7 } 8}

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

投稿2018/05/06 13:25

編集2018/05/06 13:27
kakajika

総合スコア3131

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

kazzzstudio

2018/05/06 13:54

アドバイス、ありがとうございます! ご教示いただいたコードを試してみました。 おそらくこのあたりの問題ではないかと思うのですが、まだループから抜け出せずにいます。 止まっているループ側で、 let runLoop = RunLoop.current を let runLoop = RunLoop.main に変えてみても動きませんでした。
kakajika

2018/05/08 02:12

うーん、なるほど。。Alamofireの方もGCDのmain queueに完了時の処理を投げてると思うので、main queue内で待機処理をしてしまうとキューが詰まって完了時の処理が呼ばれなくなってしまうのかもしれません。 代替案として、DispatchQueue.main.asyncの代わりにperformSelectorOnMainThreadを使ってみてはどうでしょうか? self?.performSelector(onMainThread: #selector(reloadChatData) ...) もっとも、RunLoopで同期待ち合わせっぽい処理をするのは本来の使い方ではなく挙動が保証されるものでもないので、使わない方向に変えていただくのがよさそうです。
guest

0

自己解決

解決しました。

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

Swift

1 private func runQuery(_ _url: String) -> JSON { 2 var responseData: JSON = JSON.null 3 let urlString = _url 4 var keepAlive = true 5 6 let queue = DispatchQueue(label: "com.xxxxxxxx", qos: .background, attributes: .concurrent) 7 8 Alamofire.request(urlString, method: .get, parameters: nil, encoding: JSONEncoding.default).responseJSON(queue: queue) { response in 9 responseData = JSON(response.result.value!) 10 keepAlive = false 11 } 12 13 let runLoop = RunLoop.current 14 while keepAlive && 15 runLoop.run(mode: RunLoopMode.defaultRunLoopMode, before: Date(timeIntervalSinceNow: 0.1)) { 16 } 17 18 return responseData 19 } 20

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

投稿2018/05/20 06:02

kazzzstudio

総合スコア94

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問