LoginSignup
1
2

【Swift】JSONデータをフェッチして、モデルオブジェクトを作成する

Last updated at Posted at 2023-08-09

この記事は何?

Swiftコードを使ったWebプログラミングの基本を学ぶ。

  • リクエストの作成と送信
  • データの受信とデコード
  • JSONデータからのモデルオブジェクト作成

Swiftを基礎から学ぶには
自著、工学社より発売中の「まるごと分かるSwiftプログラミング」をお勧めします。変数、関数、フロー制御構文、データ構造はもちろん、構造体からクロージャ、エクステンション、プロトコル、クロージャまでを基礎からわかりやすく解説しています。

iTunes Search API

iTunes Search APIを利用すると、iTunes StoreとApple Books Storeのコンテンツを検索できる。
検索できるコンテンツは本、映画、ポッドキャスト、音楽、ミュージックビデオ、オーディオブック、テレビ番組など多岐にわたる。
詳しい情報は、iTunes Search APIのドキュメントを参照。

検索リクエストは基本的に、以下のようなURLで構成される。
例)https://itunes.apple.com/search?parameterkeyvalue

実際は、上記URLの parameterkeyvalue 部分がクエリに置き換わる。
クエリはキー・値のペア。
例)key1=value1

クエリはアンパサンド記号&を使用して、キー・値ペアを連結できる。
例)key1=value1&key2=value2&key3=value3

クエリにはさまざまなキーがある。
以下に、termキー、entityキー、limitキーなどを使った検索クエリの例を示す。
例)デヴィッド・ボウイの曲を検索して、最初の5アイテムを返すURL
https://itunes.apple.com/search?media=music&term=david%20bowie&entity=song&limit=5

実際、ブラウザのアドレスバーに上記URLを入力すると、結果のJSONファイルをダウンロードできる。

リクエストを作成する

Swiftコードを使ってリクエストURLを構築できる。
ここでは、その方法を解説するために「デヴィッド・ボウイの曲を検索して、最初の5アイテムを返すURL」を例に挙げる。
このリクエストURLには、以下のクエリが含まれている。

  • term=david+bowie
  • media=music
  • entity=song
  • limit=5
リクエストURLを作成するSwiftコード
import Foundation

// ベースとなるURL
var urlComponents = URLComponents(string: "https://itunes.apple.com/search")!

// クエリ部分
let queryItems = ["term": "david bowie", "media": "music", "entity": "song", "limit": "5"].map {
    URLQueryItem(name: $0.key, value: $0.value)
}
// URLにクエリを設定
urlComponents.queryItems = queryItems

// 構築したリクエストURLは、`URLComponents`インスタンスの`url`プロパティで確認できる。
urlComponents.url   // https://itunes.apple.com/search?media=music&term=david%20bowie&entity=song&limit=5

リクエスト送信

リクエストを送信してWebからデータを取得するには、URLSession.shareddata(from:)メソッドを呼び出す。
その際、URLを構築したurlComponentsurlプロパティを使指定する。
メソッドの返り値は、定数のタプル(data, response)に設定する。
なお、非同期関数なので、メソッドの呼び出しにtryおよびawaitをマークする。
そして、コード全体をTaskブロックにラップする。
data(from:)メソッドが完了したら、得られたデータが有効かどうかを確認する。

リクエストを送信して、JSONデータを出力する
Task {
    let (data, response) = try await URLSession.shared.data(from: urlComponents.url!)
    
    if let httpResponse = response as? HTTPURLResponse,
       httpResponse.statusCode == 200 {
        print(data)
    }
}

dataオブジェクトをそのまま出力しても、バイナリデータのサイズが示されるだけ。

出力されたJSONデータ
7714 bytes

URLSession型

URLSessionクラスは、接続先のURLからデータをダウンロード、またはデータをアップロードするためのAPIを提供する。
アプリはこのAPIを使用して、iOSでアプリが実行されていないとき、またはアプリが一時停止している間にバックグラウンドでダウンロードを継続することもできる。

アプリはURLSession型インスタンスを作成し、それがデータ転送タスクをグループごとに調整する。
例えば、Webブラウザアプリを作成する場合、タブまたはウィンドウごとに1つのセッションを作成する。
あるいは、インタラクティブなセッションと別に、バックグラウンドダウンロードのためのセッションを作成する場合もある。
アプリは各セッションに、特定のURLリクエストを表す一連のタスクを追加する。

詳細は、URLSession開発者向けドキュメントを参照。

data(from:)メソッド

data(from:)メソッドは、指定されたURLにコンテンツを探しに行って、レスポンスとデータを非同期的に取得する。
詳細はこちらのドキュメントを参照。

data(from:delegate)メソッドの宣言
func data(
    from url: URL, 
    delegate: (URLSessionTaskDelegate)? = nil
) async throws -> (Data, URLResponse)

パラメータ

  • url
    リクエストの送信先となるURLのインスタンス。

  • delegate
    コールバックを受け取るデリゲート。
    既定値としてnilが設定されているので、省略可能。

返り値

非同期的に割り当てられる、URLコンテンツを含むData型インスタンスとレスポンスのタプル。

  • Data
    受信したデータのバイナリ。

  • URLResponse
    レスポンスに関連付けられたメタデータ。

データをデコードする

デコードとは、ある形式に符号化されたデータを元の状態に戻すこと。
例えば、動画データは記録の際に圧縮形式にエンコードされて、再生時は一時的にデコードされる。

ここでは、Webから取得したデータの具体的な内容を表示するため、文字列にデコードする。
先ほどのコードを、以下のように修正する。

レスポンスのデータを文字列にデコードする
Task {
    let (data, response) = try await URLSession.shared.data(from: urlComponents.url!)
    
    if let httpResponse = response as? HTTPURLResponse,
       httpResponse.statusCode == 200,
       let string = String(data: data, encoding: .utf8) {
        print(string)
    }
}

String(data:encoding:)イニシャライザを使って、バイナリデータを文字列にデコードする。
文字コード形式は.utf8を指定する。

init(data:encoding)イニシャライザ

dataパラメータに渡されたオブジェクトを、Unicode文字に変換した文字列を返す。
変換の際、encodingパラメータに指定されたエンコーディングを使用する。
ただし、変換できない場合はnilを返す。

init(data:encoding)イニシャライザの宣言
init?(data: Data, encoding: String.Encoding)

データを文字列にデコードしてから、コンソールに出力した結果は以下の通り。

出力結果
{
 "resultCount":5,
 "results": [
{"wrapperType":"track", "kind":"song", "artistId":3296287, "collectionId":697650603, "trackId":697651445, "artistName":"David Bowie & Queen", "collectionName":"Best of Bowie", "trackName":"Under Pressure", "collectionCensoredName":"Best of Bowie", "trackCensoredName":"Under Pressure", "collectionArtistId":551695, "collectionArtistName":"David Bowie", "collectionArtistViewUrl":"https://music.apple.com/us/artist/david-bowie/551695?uo=4", "artistViewUrl":"https://music.apple.com/us/artist/queen/3296287?uo=4", "collectionViewUrl":"https://music.apple.com/us/album/under-pressure/697650603?i=697651445&uo=4", "trackViewUrl":"https://music.apple.com/us/album/under-pressure/697650603?i=697651445&uo=4", 
"previewUrl":"https://audio-ssl.itunes.apple.com/itunes-assets/AudioPreview125/v4/88/ed/d2/88edd28f-7aa9-fc46-4ddb-0c4e9a5fd7b2/mzaf_7010235254143732396.plus.aac.p.m4a", "artworkUrl30":"https://is2-ssl.mzstatic.com/image/thumb/Features115/v4/fc/65/98/fc6598f9-706b-48a2-dc37-9854f703b21e/dj.xnockfyi.jpg/30x30bb.jpg", "artworkUrl60":"https://is2-ssl.mzstatic.com/image/thumb/Features115/v4/fc/65/98/fc6598f9-706b-48a2-dc37-9854f703b21e/dj.xnockfyi.jpg/60x60bb.jpg", "artworkUrl100":"https://is2-ssl.mzstatic.com/image/thumb/Features115/v4/fc/65/98/fc6598f9-706b-48a2-dc37-9854f703b21e/dj.xnockfyi.jpg/100x100bb.jpg", "collectionPrice":11.99, "trackPrice":1.29, "releaseDate":"1981-10-26T08:00:00Z", "collectionExplicitness":"explicit", "trackExplicitness":"notExplicit", "discCount":1, "discNumber":1, "trackCount":20, "trackNumber":13, "trackTimeMillis":237520, "country":"USA", "currency":"USD", "primaryGenreName":"Rock", "isStreamable":true}, 
{"wrapperType":"track", "kind":"song", "artistId":551695, "collectionId":697650603, "trackId":697651126, "artistName":"David Bowie", "collectionName":"Best of Bowie", "trackName":"Space Oddity", "collectionCensoredName":"Best of Bowie", "trackCensoredName":"Space Oddity", "artistViewUrl":"https://music.apple.com/us/artist/david-bowie/551695?uo=4", "collectionViewUrl":"https://music.apple.com/us/album/space-oddity/697650603?i=697651126&uo=4", "trackViewUrl":"https://music.apple.com/us/album/space-oddity/697650603?i=697651126&uo=4", 
"previewUrl":"https://audio-ssl.itunes.apple.com/itunes-assets/AudioPreview115/v4/8d/34/1d/8d341d3d-978d-8885-4807-ff12d02659ec/mzaf_17330704255932500816.plus.aac.p.m4a", "artworkUrl30":"https://is2-ssl.mzstatic.com/image/thumb/Features115/v4/fc/65/98/fc6598f9-706b-48a2-dc37-9854f703b21e/dj.xnockfyi.jpg/30x30bb.jpg", "artworkUrl60":"https://is2-ssl.mzstatic.com/image/thumb/Features115/v4/fc/65/98/fc6598f9-706b-48a2-dc37-9854f703b21e/dj.xnockfyi.jpg/60x60bb.jpg", "artworkUrl100":"https://is2-ssl.mzstatic.com/image/thumb/Features115/v4/fc/65/98/fc6598f9-706b-48a2-dc37-9854f703b21e/dj.xnockfyi.jpg/100x100bb.jpg", "collectionPrice":11.99, "trackPrice":1.29, "releaseDate":"1969-07-01T07:00:00Z", "collectionExplicitness":"explicit", "trackExplicitness":"notExplicit", "discCount":1, "discNumber":1, "trackCount":20, "trackNumber":1, "trackTimeMillis":316333, "country":"USA", "currency":"USD", "primaryGenreName":"Rock", "isStreamable":true}, 
{"wrapperType":"track", "kind":"song", "artistId":551695, "collectionId":697650603, "trackId":697651447, "artistName":"David Bowie", "collectionName":"Best of Bowie", "trackName":"Let's Dance (Single Version)", "collectionCensoredName":"Best of Bowie", "trackCensoredName":"Let's Dance (Single Version)", "artistViewUrl":"https://music.apple.com/us/artist/david-bowie/551695?uo=4", "collectionViewUrl":"https://music.apple.com/us/album/lets-dance-single-version/697650603?i=697651447&uo=4", "trackViewUrl":"https://music.apple.com/us/album/lets-dance-single-version/697650603?i=697651447&uo=4", 
"previewUrl":"https://audio-ssl.itunes.apple.com/itunes-assets/AudioPreview115/v4/f0/f6/ab/f0f6abb6-09d7-ee3d-a0a9-194371032711/mzaf_6603400039706973488.plus.aac.p.m4a", "artworkUrl30":"https://is2-ssl.mzstatic.com/image/thumb/Features115/v4/fc/65/98/fc6598f9-706b-48a2-dc37-9854f703b21e/dj.xnockfyi.jpg/30x30bb.jpg", "artworkUrl60":"https://is2-ssl.mzstatic.com/image/thumb/Features115/v4/fc/65/98/fc6598f9-706b-48a2-dc37-9854f703b21e/dj.xnockfyi.jpg/60x60bb.jpg", "artworkUrl100":"https://is2-ssl.mzstatic.com/image/thumb/Features115/v4/fc/65/98/fc6598f9-706b-48a2-dc37-9854f703b21e/dj.xnockfyi.jpg/100x100bb.jpg", "collectionPrice":11.99, "trackPrice":1.29, "releaseDate":"1983-03-07T08:00:00Z", "collectionExplicitness":"explicit", "trackExplicitness":"notExplicit", "discCount":1, "discNumber":1, "trackCount":20, "trackNumber":14, "trackTimeMillis":245427, "country":"USA", "currency":"USD", "primaryGenreName":"Rock", "isStreamable":true}, 
{"wrapperType":"track", "kind":"song", "artistId":551695, "collectionId":697650603, "trackId":697651127, "artistName":"David Bowie", "collectionName":"Best of Bowie", "trackName":"Changes", "collectionCensoredName":"Best of Bowie", "trackCensoredName":"Changes", "artistViewUrl":"https://music.apple.com/us/artist/david-bowie/551695?uo=4", "collectionViewUrl":"https://music.apple.com/us/album/changes/697650603?i=697651127&uo=4", "trackViewUrl":"https://music.apple.com/us/album/changes/697650603?i=697651127&uo=4", 
"previewUrl":"https://audio-ssl.itunes.apple.com/itunes-assets/AudioPreview115/v4/12/57/8d/12578d54-7b2a-0e63-5c89-0f7628cc4b15/mzaf_17685057866611130999.plus.aac.p.m4a", "artworkUrl30":"https://is2-ssl.mzstatic.com/image/thumb/Features115/v4/fc/65/98/fc6598f9-706b-48a2-dc37-9854f703b21e/dj.xnockfyi.jpg/30x30bb.jpg", "artworkUrl60":"https://is2-ssl.mzstatic.com/image/thumb/Features115/v4/fc/65/98/fc6598f9-706b-48a2-dc37-9854f703b21e/dj.xnockfyi.jpg/60x60bb.jpg", "artworkUrl100":"https://is2-ssl.mzstatic.com/image/thumb/Features115/v4/fc/65/98/fc6598f9-706b-48a2-dc37-9854f703b21e/dj.xnockfyi.jpg/100x100bb.jpg", "collectionPrice":11.99, "trackPrice":1.29, "releaseDate":"1971-12-17T08:00:00Z", "collectionExplicitness":"explicit", "trackExplicitness":"notExplicit", "discCount":1, "discNumber":1, "trackCount":20, "trackNumber":2, "trackTimeMillis":214573, "country":"USA", "currency":"USD", "primaryGenreName":"Rock", "isStreamable":true}, 
{"wrapperType":"track", "kind":"song", "artistId":551695, "collectionId":697650603, "trackId":697651436, "artistName":"David Bowie", "collectionName":"Best of Bowie", "trackName":"Heroes (Single Version)", "collectionCensoredName":"Best of Bowie", "trackCensoredName":"Heroes (Single Version)", "artistViewUrl":"https://music.apple.com/us/artist/david-bowie/551695?uo=4", "collectionViewUrl":"https://music.apple.com/us/album/heroes-single-version/697650603?i=697651436&uo=4", "trackViewUrl":"https://music.apple.com/us/album/heroes-single-version/697650603?i=697651436&uo=4", 
"previewUrl":"https://audio-ssl.itunes.apple.com/itunes-assets/AudioPreview115/v4/9c/dd/87/9cdd87f6-378e-d0bf-dd77-3dac5e8cf137/mzaf_5403030196739660265.plus.aac.p.m4a", "artworkUrl30":"https://is2-ssl.mzstatic.com/image/thumb/Features115/v4/fc/65/98/fc6598f9-706b-48a2-dc37-9854f703b21e/dj.xnockfyi.jpg/30x30bb.jpg", "artworkUrl60":"https://is2-ssl.mzstatic.com/image/thumb/Features115/v4/fc/65/98/fc6598f9-706b-48a2-dc37-9854f703b21e/dj.xnockfyi.jpg/60x60bb.jpg", "artworkUrl100":"https://is2-ssl.mzstatic.com/image/thumb/Features115/v4/fc/65/98/fc6598f9-706b-48a2-dc37-9854f703b21e/dj.xnockfyi.jpg/100x100bb.jpg", "collectionPrice":11.99, "trackPrice":1.29, "releaseDate":"1975-11-21T08:00:00Z", "collectionExplicitness":"explicit", "trackExplicitness":"notExplicit", "discCount":1, "discNumber":1, "trackCount":20, "trackNumber":10, "trackTimeMillis":217360, "country":"USA", "currency":"USD", "primaryGenreName":"Rock", "isStreamable":true}]
}

データモデルのオブジェクトを作成する

APIから得られたデータは、辞書(resultsキー、値は配列)の構造になっている。
具体的なデータが格納されているのは配列部分。
配列部分を収容するモデルオブジェクトの構造体を作成する。

今回、モデルを定義するために必要なプロパティは以下に絞ることにする。

  • trackName(曲名)
  • artistName(アーティスト名)
  • kind(メディアの種類)
  • artworkUrl100(アートワークのURL)

これらのプロパティを使用して、以下のようにStoreItem構造体を定義する。

データモデルのStoreItem型
struct StoreItem: Codable {
        let name: String
        let artist: String
        let kind: String
        let artworkURL: String
        
        enum CodingKeys: String, CodingKey {
            case name = "trackName"
            case artist = "artistName"
            case kind
            case artworkURL = "artworkUrl100"
        }
}

StoreItem型はCodableプロトコルに適合させる。
そして、CodingKeys列挙型を使用してJSONキーをモデル上のより適切なプロパティ名にマッピングする。

レスポンスからモデルインスタンスを作成する

JSONレスポンスからモデルインスタンスを作成するための値を取得する方法を検討する。
モデルインスタンスの作成に必要なデータは、resultsキーにネストされている。
そのデータ形式は、モデルの配列である[StoreItem]型。

そのためには、レスポンス自体をSearchResponse型として受け取る。

データモデルを配列に格納するSearchResponse型
struct SearchResponse: Codable {
    let results: [StoreItem]
}

JSONDecoderを利用する

JSONDecoderは、JSONオブジェクトからデータ型のインスタンスをデコードできる。
ただし、JSONDecoderインスタンスを使用してデコードを行うには、型がCodableに準拠すること。

デコーダーを使って、JSONオブジェクトをSearchResponse型インスタンスにデコードする
Task {
    let (data, response) = try await URLSession.shared.data(from: urlComponents.url!)
    
    if let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 {
        // デコーダーを使ってモデルオブジェクトにデコードする
        let decoder = JSONDecoder()
        let searchResponse = try decoder.decode(SearchResponse.self, from: data)

        // モデルオブジェクトの配列を出力する
        searchResponse.results.forEach { item in
            print("""
                  Name: \(item.name)
                  Artist: \(item.artist)
                  Kind: \(item.kind)
                  Artwork URL: \(item.artworkURL)
                  
                  """)
        }
    }
}

モデルオブジェクトの作成は、デコーダーのdecode(:from:)メソッドによってデコードと同時に行われている。
以下は、作成されたモデルオブジェクトの出力。

出力されたモデルオブジェクト
Name: Under Pressure
Artist: David Bowie & Queen
Kind: song
Artwork URL: https://is2-ssl.mzstatic.com/image/thumb/Features115/v4/fc/65/98/fc6598f9-706b-48a2-dc37-9854f703b21e/dj.xnockfyi.jpg/100x100bb.jpg

Name: Space Oddity
Artist: David Bowie
Kind: song
Artwork URL: https://is2-ssl.mzstatic.com/image/thumb/Features115/v4/fc/65/98/fc6598f9-706b-48a2-dc37-9854f703b21e/dj.xnockfyi.jpg/100x100bb.jpg

Name: Let's Dance (Single Version)
Artist: David Bowie
Kind: song
Artwork URL: https://is2-ssl.mzstatic.com/image/thumb/Features115/v4/fc/65/98/fc6598f9-706b-48a2-dc37-9854f703b21e/dj.xnockfyi.jpg/100x100bb.jpg

Name: Changes
Artist: David Bowie
Kind: song
Artwork URL: https://is2-ssl.mzstatic.com/image/thumb/Features115/v4/fc/65/98/fc6598f9-706b-48a2-dc37-9854f703b21e/dj.xnockfyi.jpg/100x100bb.jpg

Name: Dancing In the Street
Artist: David Bowie & Mick Jagger
Kind: song
Artwork URL: https://is2-ssl.mzstatic.com/image/thumb/Features115/v4/fc/65/98/fc6598f9-706b-48a2-dc37-9854f703b21e/dj.xnockfyi.jpg/100x100bb.jpg

decode()メソッド

指定された型にJSONオブジェクトをデコードして、返す。
ただし、元データが有効なJSONでなかった場合、メソッドはDecodingError.dataCorrupted(_:)エラーをスローする。
JSONのデコードに失敗した場合は、それに起因するエラーをスローする。

decode()メソッドの宣言
func decode<T>(
    _ type: T.Type,
    from data: Data
) throws -> T where T : Decodable

パラメータ

  • type
    JSONオブジェクトをデコードした結果となる型

  • data
    デコードしたいJSOオブジェクト。

返り値

デコーダーがdataを解析できた場合、typeパラメータに指定した型の値を返す。

フェッチ機能を抽象化する

リクエストの作成、レスポンスの解析、デコードといったネットワーキング・コードを抽象化するために、フェッチ関数を定義する。

この新しいfetchItems()関数は、クエリ辞書を受け取り、StoreItemsの配列を返す。

非同期的な通信とエラー対処のため、宣言にはasyncthrowsをマークする。

fetchItems(matching:)関数の宣言
func tetchItems(
    matching query: [String: String]
) async throws -> [StoreItem] 

この関数はクエリを受け取って、URLを作成する。
そして、リクエストが完了すると、ステータスコードをチェックした上で、JSONをモデルオブジェクトにデコードする。
具体的には、fetchItems(matching)関数を以下のように実装できる。

fetchItems(matching:)関数の実装
// ErrorとLocalizedErrorプロトコルを採用する列挙型
enum StoreItemError: Error, LocalizedError {
    case itemsNotFound
}

func tetchItems( matching query: [String: String] ) async throws -> [StoreItem] {
    // 変数urlComponentsの宣言をここに移動
    var urlComponents = URLComponents(string: "https://itunes.apple.com/search")!
    // 渡されたqueryパラメータからqueryItemsを設定する。
    let queryItems = query.map {
        URLQueryItem(name: $0.key, value: $0.value)
    }
    urlComponents.queryItems = queryItems

    // リクエストを送信するために、URLSession.sharedのdata()メソッドを呼び出す
    let (data, response) = try await URLSession.shared.data(from: urlComponents.url!)
    
    // レスポンスのステータスコードが200でなければ、エラーをスローして終了
    guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
        throw StoreItemError.itemNotFound
    }

    // JSONDecoderを作成し、`SearchResponse`オブジェクトにデコードする
    let decoder = JSONDecoder()
    let searchResponse = try decoder.decode(SearchResponse.self, from: data)
    
    return searchResponse.results
}

フェッチ関数を呼び出す

let query = ["term": "david bowie",
             "media": "music",
             "entity": "song",
             "limit": "5"]

Task {
    do {
        let storeItems = try await fetchItems(matching: query)
        storeItems.forEach { item in
            print(
                 """
                 Name: \(item.name)
                 Artist: \(item.artist)
                 Kind: \(item.kind)
                 Artwork URL: \(item.artworkURL)

                 """)
        }
    } catch {
        print(error)
    }
}

実行した結果は、以下の通り。

出力結果
Name: Under Pressure
Artist: David Bowie & Queen
Kind: song
Artwork URL: https://is2-ssl.mzstatic.com/image/thumb/Features115/v4/fc/65/98/fc6598f9-706b-48a2-dc37-9854f703b21e/dj.xnockfyi.jpg/100x100bb.jpg

Name: Space Oddity
Artist: David Bowie
Kind: song
Artwork URL: https://is2-ssl.mzstatic.com/image/thumb/Features115/v4/fc/65/98/fc6598f9-706b-48a2-dc37-9854f703b21e/dj.xnockfyi.jpg/100x100bb.jpg

Name: Let's Dance (Single Version)
Artist: David Bowie
Kind: song
Artwork URL: https://is2-ssl.mzstatic.com/image/thumb/Features115/v4/fc/65/98/fc6598f9-706b-48a2-dc37-9854f703b21e/dj.xnockfyi.jpg/100x100bb.jpg

Name: Changes
Artist: David Bowie
Kind: song
Artwork URL: https://is2-ssl.mzstatic.com/image/thumb/Features115/v4/fc/65/98/fc6598f9-706b-48a2-dc37-9854f703b21e/dj.xnockfyi.jpg/100x100bb.jpg

Name: Heroes (Single Version)
Artist: David Bowie
Kind: song
Artwork URL: https://is2-ssl.mzstatic.com/image/thumb/Features115/v4/fc/65/98/fc6598f9-706b-48a2-dc37-9854f703b21e/dj.xnockfyi.jpg/100x100bb.jpg

完成

全体のコード
import Foundation

struct StoreItem: Codable {
    let name: String
    let artist: String
    let kind: String
    let artworkURL: String
    
    enum CodingKeys: String, CodingKey {
        case name = "trackName"
        case artist = "artistName"
        case kind
        case artworkURL = "artworkUrl100"
    }
}

struct SearchResponse: Codable {
    let results: [StoreItem]
}

enum StoreItemError: Error, LocalizedError {
    case itemsNotFound
}

func fetchItems(matching query: [String: String]) async throws -> [StoreItem]  {
    var urlComponents = URLComponents(string: "https://itunes.apple.com/search")!
    urlComponents.queryItems = query.map {
        URLQueryItem(name: $0.key, value: $0.value)
    }
    
    let (data, response) = try await URLSession.shared.data(from: urlComponents.url!)
    
    guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
        throw StoreItemError.itemsNotFound
    }
    
    let decoder = JSONDecoder()
    let searchResponce = try decoder.decode(SearchResponse.self, from: data)
    
    return searchResponce.results
}

let query = ["term": "david bowie",
             "media": "music",
             "entity": "song",
             "limit": "5"]

Task {
    do {
        let storeItems = try await fetchItems(matching: query)
        storeItems.forEach { item in
            print(
                """
                Name: \(item.name)
                Artist: \(item.artist)
                Kind: \(item.kind)
                Artwork URL: \(item.artworkURL)

                """)
        }
    } catch {
        print(error)
    }
}
1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2