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

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

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

複数の計算が同時に実行される手法

非同期処理

非同期処理とは一部のコードを別々のスレッドで実行させる手法です。アプリケーションのパフォーマンスを向上させる目的でこの手法を用います。

Swift

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

API

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

Q&A

解決済

1回答

1069閲覧

【Swift】AlamofireでのAPIデータ取得時に値が返ってくる前に次の処理がされてしまいます

syoco0330

総合スコア30

並列処理

複数の計算が同時に実行される手法

非同期処理

非同期処理とは一部のコードを別々のスレッドで実行させる手法です。アプリケーションのパフォーマンスを向上させる目的でこの手法を用います。

Swift

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

API

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

0グッド

0クリップ

投稿2020/01/19 11:17

楽天booksAPIで本の情報を取得したいのですが、
APIでの本の取得が終わる前に次の処理が走ってしまって上手くいきません。

実現したいことはデバックエリアに
テスト1→テスト2→テスト3→テスト4
の流れで表示させたいのですが、

現状では
テスト1→テスト2→テスト4→テスト3
の流れになってしまいます。

APIで取得したJSON解析のコードは実装済みです。

色々調べた結果、クロージャを用いた処理をするのだと思いますが実装方法がわかりません。
ご教授いただけたら幸いです。
よろしくお願いいたします。

実行結果

ViewControllet

1// 検索ボタンを押した時の処理 2func searchBarSearchButtonClicked(_ searchBar: UISearchBar) { 3 view.endEditing(true) 4 if let seachWord = searchField.text { 5 let search = ScanText(searchWord: seachWord) 6 print("テスト1") 7 search.search() 8 print("テスト2") 9 var bookimageString = search.bookimageString 10 var bookTitleString = search.bookTitleString 11 var authorsString = search.authorsString 12 var priceString = search.priceString 13 print("テスト4") 14 } 15}

ScanSwift

1import Foundation 2import SwiftyJSON 3import Alamofire 4 5class ScanText { 6 7 var searchWord:String! = "" 8 9 var scanData = "" 10 var bookimageString = String() 11 var bookTitleString = String() 12 var authorsString = String() 13 var priceString = String() 14 15 var userID = String() 16 var userName = String() 17 18 19 init(searchWord:String) { 20 21 self.searchWord = searchWord 22 23 } 24 25 func search(){ 26 27 28 // エンコードする 29 let encordUrlString:String = searchWord.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)! 30 let url = "https://app.rakuten.co.jp/services/api/BooksBook/Search/20170404?format=json&title=(encordUrlString)&applicationId=1032673171825430477" 31 // let url = "https://app.rakuten.co.jp/services/api/BooksBook/Search/20170404?format=json&isbn=(scanData)&applicationId=1032673171825430477" 32 33 // Alamofireでhttpリクエストを送る 34 Alamofire.request(url, method: .get, parameters: nil, encoding: JSONEncoding.default).responseJSON {(response) in 35 36 switch response.result{ 37 case .success: 38 let json:JSON = JSON(response.data as Any) 39 40 // 本のサムネイル取得 41 var imageString = json["Items"][0]["Item"]["mediumImageUrl"].string 42 if imageString == nil{ 43 imageString = "" 44 } 45 // 本のタイトルを取得 46 var bookTitle = json["Items"][0]["Item"]["title"].string 47 if bookTitle == nil{ 48 bookTitle = "" 49 } 50 // 著者を取得 51 var authors = json["Items"][0]["Item"]["author"].string 52 if authors == nil{ 53 authors = "" 54 } 55 56 // 本の値段を取得 57 var intPrice = json["Items"][0]["Item"]["itemPrice"] 58 var price = intPrice.description 59 if price == nil { 60 price = "" 61 } 62 63 print("テスト3") 64 // 取得した情報を入れる 65 self.bookimageString = imageString! 66 self.bookTitleString = bookTitle! 67 self.authorsString = authors! 68 self.priceString = price 69 70 71 break 72 case .failure(let error): 73 print(error) 74 break 75 76 } 77 } 78 } 79}

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

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

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

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

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

guest

回答1

0

ベストアンサー

APIとの非同期処理については様々な実装方法があると思うので、一例としてAlamofireとPromiseKit(https://github.com/mxcl/PromiseKit)を使った方法を紹介します。
PromiseKitはPromiseパターンを実装するためのライブラリですが単純にコールバック処理をいい感じに書けます。
(あくまで一例なので最適解かどうかは自分も分かりません。)


(前提としてbookimageStringなど必要な情報を持つApiResという構造体があるとします)
出来るだけ元のコードを崩さず書くと↓こんな感じで行けると思います。

swift

1class ScanText { 2 func search(searchWord: String){ 3 let promise: Promise<ApiRes?> = Promise { resolver in 4 Alamofire.request(url, method: .get, parameters: nil, encoding: JSONEncoding.default).responseJSON {(response) in 5 switch response.result{ 6 case .success: 7 let json:JSON = JSON(response.data as Any) 8 9 // 本のサムネイル取得 10 var imageString = json["Items"][0]["Item"]["mediumImageUrl"].string 11 if imageString == nil{ 12 imageString = "" 13 } 14 // 本のタイトルを取得 15 var bookTitle = json["Items"][0]["Item"]["title"].string 16 if bookTitle == nil{ 17 bookTitle = "" 18 } 19 // 著者を取得 20 var authors = json["Items"][0]["Item"]["author"].string 21 if authors == nil{ 22 authors = "" 23 } 24 25 // 本の値段を取得 26 var intPrice = json["Items"][0]["Item"]["itemPrice"] 27 var price = intPrice.description 28 if price == nil { 29 price = "" 30 } 31 32 print("テスト3") 33 // 取得した情報を入れる 34 ApiRes res = ApiRes() 35 res.bookimageString = imageString! 36 res.bookTitleString = bookTitle! 37 res.authorsString = authors! 38 res.priceString = price 39 40 resolver.fulfill(res); 41 42 case .failure(let error): 43 resolver.reject(error); 44 } 45 } 46 } 47 } 48}

呼び出し元はこんな感じ↓

swift

1let search = ScanText() 2search.search(searchWord: seachWord) 3 .done { res in 4 //APIの通信成功後ここが呼ばれる resolver.fulfill(res)のresが渡ってくる 5 }.catch {error in 6 //APIの通信失敗後ここが呼ばれる resolver.reject(error)のresが渡ってくる 7 }

今回のパターンだとdoneの中でテスト4を表示するれば順番通りになるかと思います。

余談ですが、今回追加した構造体(ApiRes)はCodableを継承してあげるとJSONDecoderでJSONからクラスへ簡単にデコードできるのでオススメです。参考
(json["Items"][0]["Item"]["mediumImageUrl"].string 等の処理が一切不要になる)

投稿2020/01/20 06:16

bobmax

総合スコア133

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

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

syoco0330

2020/01/20 12:11

ご丁寧に解説していただきありがとうございます。 import PromiseKitをして コードを記述してみたのですが エラーが3点出てしまいました。 構造体が記述できていないからでしょうか? もし可能であれば改善方法について教えていただけないでしょうか。 エラーの箇所か下記です エラー箇所:Promise<ApiRes?> エラー文:Use of undeclared type 'ApiRes' エラー箇所:ApiRes res = ApiRes() エラー文:Consecutive statements on a line must be separated by ';' エラー箇所: let search = ScanText() エラー文:Missing argument for parameter 'searchWord' in call です。 何卒よろしくお願いいたします。
bobmax

2020/01/21 00:04

①構造体が記述できていないからでしょうか? ということはApiResを作っていないということでしょうか?あえて記載しませんでしたが、呼び出し元に値を渡すためにApiResを実装してください。 struct ApiRes { var bookimageString: String -以下略- } こんな感じに入れ物を用意してここに値を突っ込んで呼び出し元に渡してあげるイメージです。 ②初期値なしの構造体の場合、ApiRes res = ApiRes()ではなくて ApiRes res = ApiRes(bookimageString: "値", ~以下略)こんな感じでしたね。失礼しました。 ③イニシャライザのsearchWordが残ってませんか?自分の例ではメンバに持たなくてもいいやと思ってメソッドの引数にsearchWordをもってきてます。別にイニシャライザで渡してあげても大丈夫ですよ。
syoco0330

2020/01/22 12:19

コメントのご返答ありがとうございます。 教えていただいた実装方法を試していたら遅くなってしまいました。 ①構造体 ②初期値なし の件は実装ができました。 ②初期値なし はエラーが出てしまい、 var res = ApiRes(bookimageString: imageString!, bookTitleString: bookTitle!, authorsString: authors!, priceString: price) で実装しました。 最後の③イニシャライザ の部分は 下記のエラーが出てしまい、解消ができていない状態です。泣 なんとか自力で解消しようと思いましたが、上手くいきません。 ご教授いただけませんでしょうか。 エラー内容 Value of tuple type '()' has no member 'done' 該当のソースコード if let seachWord = searchField.text { et search = ScanText() search.search(searchWord: seachWord) .done { res in }.catch {error in } }
bobmax

2020/01/23 00:38

申し訳ないです。気軽にmacが使える環境にいないものでテキストエディタで適当に書いていたのですが、先ほどxcodeで試したところ凡ミスに気づきました・・・。 search()メソッドの戻り値としてpromiseを返してください。promise実装しただけで何もしていませんでした・・・。 func search(searchWord: String) -> Promise<ApiRes?> { //~略~ //最後にpromiseを返す return promise } これで行けるはずです。 無駄にお時間を取らせてしまい申し訳ないです。
syoco0330

2020/01/23 10:08

希望通りの実装ができました! 教えていただけなかったらもっと時間がかかっていたので、大変助かりました! 最後までお付き合いいただきありがとうございました!
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問