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

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

ただいまの
回答率

89.05%

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

解決済

回答 1

投稿

  • 評価
  • クリップ 0
  • VIEW 377

syoco0330

score 13

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

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

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

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

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

実行結果
// 検索ボタンを押した時の処理
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
    view.endEditing(true)
    if let seachWord = searchField.text {
        let search  = ScanText(searchWord: seachWord)
        print("テスト1")
          search.search()
        print("テスト2")
        var bookimageString = search.bookimageString
        var bookTitleString = search.bookTitleString
        var authorsString = search.authorsString
        var priceString = search.priceString
        print("テスト4")
    }
}
import Foundation
import SwiftyJSON
import Alamofire

class ScanText {

    var searchWord:String! = ""

    var scanData = ""
    var bookimageString = String()
    var bookTitleString = String()
    var authorsString = String()
    var priceString = String()

    var userID = String()
    var userName = String()


    init(searchWord:String) {

        self.searchWord = searchWord

    }

    func search(){


        // エンコードする
        let encordUrlString:String = searchWord.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
        let url = "https://app.rakuten.co.jp/services/api/BooksBook/Search/20170404?format=json&title=\(encordUrlString)&applicationId=1032673171825430477"
           // let url = "https://app.rakuten.co.jp/services/api/BooksBook/Search/20170404?format=json&isbn=\(scanData)&applicationId=1032673171825430477"

        // Alamofireでhttpリクエストを送る
         Alamofire.request(url, method: .get, parameters: nil, encoding: JSONEncoding.default).responseJSON {(response) in

             switch response.result{
             case .success:
                 let json:JSON = JSON(response.data as Any)

                 // 本のサムネイル取得
                 var imageString = json["Items"][0]["Item"]["mediumImageUrl"].string
                 if imageString == nil{
                     imageString = ""
                 }
                 // 本のタイトルを取得
                 var bookTitle = json["Items"][0]["Item"]["title"].string
                 if bookTitle == nil{
                     bookTitle = ""
                 }
                 // 著者を取得
                 var authors = json["Items"][0]["Item"]["author"].string
                 if authors == nil{
                     authors = ""
                 }

                 // 本の値段を取得
                 var intPrice = json["Items"][0]["Item"]["itemPrice"]
                 var price  = intPrice.description
                 if price == nil {
                     price = ""
                 }

                 print("テスト3")
                 // 取得した情報を入れる
                 self.bookimageString = imageString!
                 self.bookTitleString = bookTitle!
                 self.authorsString = authors!
                 self.priceString = price


                 break
             case .failure(let error):
                 print(error)
                 break

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

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

回答 1

checkベストアンサー

+1

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


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

class ScanText {
    func search(searchWord: String){
        let promise: Promise<ApiRes?> = Promise { resolver in
            Alamofire.request(url, method: .get, parameters: nil, encoding: JSONEncoding.default).responseJSON {(response) in
                switch response.result{
                case .success:
                    let json:JSON = JSON(response.data as Any)

                    // 本のサムネイル取得
                    var imageString = json["Items"][0]["Item"]["mediumImageUrl"].string
                    if imageString == nil{
                        imageString = ""
                    }
                    // 本のタイトルを取得
                    var bookTitle = json["Items"][0]["Item"]["title"].string
                    if bookTitle == nil{
                        bookTitle = ""
                    }
                    // 著者を取得
                    var authors = json["Items"][0]["Item"]["author"].string
                    if authors == nil{
                        authors = ""
                    }

                    // 本の値段を取得
                    var intPrice = json["Items"][0]["Item"]["itemPrice"]
                    var price  = intPrice.description
                    if price == nil {
                        price = ""
                    }

                    print("テスト3")
                    // 取得した情報を入れる
                    ApiRes res = ApiRes()
                    res.bookimageString = imageString!
                    res.bookTitleString = bookTitle!
                    res.authorsString = authors!
                    res.priceString = price

                    resolver.fulfill(res);

                case .failure(let error):
                    resolver.reject(error);
                }
            }
        }
    }
}


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

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


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

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

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2020/01/22 21: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

    }
    }

    キャンセル

  • 2020/01/23 09:38

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

    キャンセル

  • 2020/01/23 19:08

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

    キャンセル

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

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

関連した質問

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

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