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

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

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

Xcodeはソフトウェア開発のための、Appleの統合開発環境です。Mac OSXに付随するかたちで配布されています。

Swift

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

API

APIはApplication Programming Interfaceの略です。APIはプログラムにリクエストされるサービスがどのように動作するかを、デベロッパーが定めたものです。

Q&A

解決済

1回答

2564閲覧

APIからのレスポンス取得がタイムアウト後になってしまう:Alamofire4 Xcode8 swift3

tukusa

総合スコア44

Xcode

Xcodeはソフトウェア開発のための、Appleの統合開発環境です。Mac OSXに付随するかたちで配布されています。

Swift

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

API

APIはApplication Programming Interfaceの略です。APIはプログラムにリクエストされるサービスがどのように動作するかを、デベロッパーが定めたものです。

0グッド

0クリップ

投稿2017/04/03 08:25

編集2017/04/03 08:57

使用環境
Xcode8.2.1
swift3.0
Alamofire4

swift

1 //APIからデータを取得 2 func getData() -> Bool { 3 var isComplete = false 4 var isError = true 5 //APIからデータ取得 6 Alamofire.request(ApiConst.Url, 7 parameters: [ ApiConst.Key: ApiConst.Val] 8 ).responseJSON {response in 9 print("データ取得") 10 if response.result.isSuccess { 11 isError = false 12 } else { 13 isError = true 14 } 15 isComplete = true 16 } 17 18 let calendar = Calendar(identifier: .gregorian) 19 let startTime = Date() 20 var isTimeout = false 21 let runLoop = RunLoop.current 22 //レスポンスがあるか、タイムリミットが尽きるまで待機する(0.1秒刻み) 23 while !isComplete && !isTimeout && 24 runLoop.run(mode: RunLoopMode.defaultRunLoopMode, before: NSDate(timeIntervalSinceNow: 0.1) as Date) { 25 //開始時間から現在までの時間のスパンがタイムリミットに達したらタイムアウトフラグを立てる 26 let nowTime = Date() 27 let span = calendar.dateComponents([.second], from: startTime, to: nowTime).second 28 if span! > ApiConst.LimitTime { 29 isTimeout = true 30 print("タイムアウト") 31 } 32 } 33 print("データ返却") 34 if isComplete && !isError { 35 print("成功") 36 return true 37 } else { 38 print("失敗") 39 return false 40 } 41 }

上記はプロトコル・デリゲートを使用せず、ちと強引に取得をreturn前に終わらせている例です。

--

swift

1 @IBAction func getAPIAction(_ sender: Any) { 2 _ = self.getData() 3 }

上記の使用方法では、予定通りの動作をしてくれます。

-- Print Log --
データ取得
データ返却
成功

--

swift

1 @IBAction func getAPIAction(_ sender: Any) { 2 let alert = UIAlertController(title: "", message: "取得する", 3 preferredStyle: UIAlertControllerStyle.alert) 4 let yesAction = UIAlertAction(title: "OK", style: UIAlertActionStyle.default, 5 handler: {(_) -> Void in 6 _ = self.getData() 7 }) 8 alert.addAction(yesAction) 9 self.present(alert, animated: true, completion: nil) 10 }

しかし、上記の使用方法では、レスポンスの取得が全ての動作後になってしまいます。
-- Print Log --
タイムアウト
データ返却
失敗
データ取得

Dispatch等も調べて試してみたでのすが、どうにもうまくいかず・・・。
どなた様か、コレの解消方法にお気づきになられました方、ご教授お願いします。

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

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

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

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

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

guest

回答1

0

ベストアンサー

非同期通信しているんでしょうから、データ受信後の処理は、

swift

1isComplete = true

のところに書かないと。

下記のコードは、1.request-2.requested-3.receivedの順に実行されます。

swift

1print("1.request") 2Alamofire.request(...).responseJSON {response in 3 print("3.received") 4} 5print("2.requested")

ということでしょうか?

再チャレンジ

呼び出し元が、受信後の処理をクロージャで渡します。
(Swift2 + 古いAlamofireでのコードなので注意)

swift

1//呼び出し元 2self.getData { response in 3 print("受信後の処理") 4}

swift

1func getData(completionHandler: Response<AnyObject, NSError> -> Void) -> Bool { 2 : 3 Alamofire.request(...) 4 //渡されたcompletionHandlerをそのまま渡して実行する 5 .responseJSON(completionHandler: completionHandler) 6 : 7 return true 8}

ということでしょうか?

投稿2017/04/03 08:40

編集2017/04/03 09:27
fuzzball

総合スコア16731

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

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

tukusa

2017/04/03 08:56

回答ありがとうございます。 失礼しました。説明不足でした。 1つ目のコードは、実際はDataSourceとして結果を返す別クラスでして、 if response.result.isSuccess { isError = false    実際はココで取得データを保持し、 } else { isError = true } その後、 isComplete = true を受け、 while !isComplete && !isTimeout && runLoop.run にて回し続けているループを解除し、リターンで取得したデータを返すという強引な方法を使っています。 ※ fuzzball様の言われましたように、print("3.received")の位置でプロトコルか何かで値を返せば問題なく動くのですが、、、、 なんとか1メソッドで値を返せないかなと思い、チャレンジしてしまいました。
fuzzball

2017/04/03 09:27

追記してみました。
tukusa

2017/04/03 10:03 編集

このようなソースに再度ご回答ありがとうございます。 データソース func getData(completionHandler: ((Any) -> Void)?) { //APIからデータ取得 Alamofire.request(ApiConst.Url, parameters: [ ApiConst.Key: ApiConst.Val] ).responseJSON {response in if let completion = completionHandler { completion(response) } } } コントローラー let alert = UIAlertController(title: "", message: "取得する", preferredStyle: UIAlertControllerStyle.alert) let yesAction = UIAlertAction(title: UIString.alertYesAction, style: UIAlertActionStyle.default, handler: {(_) -> Void in self.getData(completionHandler: { response in print(response) }) }) alert.addAction(yesAction) self.present(alert, animated: true, completion: nil) 実際、上記のコードで動かしてみました。 こちらは予定通りの動作をしてくれました。 やはりプロトコルインターフェイスやハンドラー(クロージャ)などで結果を返す(動作させる)のが正道なのですよね。 C#なんかの let res = await〜の様に、リポジトリで受け取り方を気にせずにデータソースから値だけを取得できれば、、と思ったのですが、 1リターンで取得結果を返すのは、諦めるべきなのでしょうか。。 改めて、ご回答ありがとうございました。
fuzzball

2017/04/03 10:16

イマイチ質問を理解出来ていないようで申し訳ないです。 もしかして、同期通信をしたいということでしょうか?
tukusa

2017/04/03 10:23

です。 実質、同期通信 レスポンスがあるか、タイムアウトまで待機する様な動作をさせたかったのです。 回答一文目に「非同期通信しているんでしょうから、」を見落としていました。。。。 余分にお手数かけてしまいました。 申し訳ありません。
fuzzball

2017/04/03 10:30

「swift semaphore 同期通信」あたりでググってみて下さい。
tukusa

2017/04/03 10:49

ありがとうございます。 セマフォというものを初めて知りました! 記事、いくつか読ませて頂きました。 残念ながらalamofireとは相性が悪いみたいですが(自分と同じループ作戦やってる方が上位に居て、悲しみを覚えました…。)、 どうにかこちらのほうを使って同期通信を作ってみようと思います。 ベストアンサーを贈らせていただきます ありがとうございました
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問