この記事は何?
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
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.shared
のdata(from:)
メソッドを呼び出す。
その際、URLを構築したurlComponents
のurl
プロパティを使指定する。
メソッドの返り値は、定数のタプル(data, response)
に設定する。
なお、非同期関数なので、メソッドの呼び出しにtry
およびawait
をマークする。
そして、コード全体をTask
ブロックにラップする。
data(from:)
メソッドが完了したら、得られたデータが有効かどうかを確認する。
Task {
let (data, response) = try await URLSession.shared.data(from: urlComponents.url!)
if let httpResponse = response as? HTTPURLResponse,
httpResponse.statusCode == 200 {
print(data)
}
}
data
オブジェクトをそのまま出力しても、バイナリデータのサイズが示されるだけ。
7714 bytes
URLSession型
URLSession
クラスは、接続先のURLからデータをダウンロード、またはデータをアップロードするためのAPIを提供する。
アプリはこのAPIを使用して、iOSでアプリが実行されていないとき、またはアプリが一時停止している間にバックグラウンドでダウンロードを継続することもできる。
アプリはURLSession
型インスタンスを作成し、それがデータ転送タスクをグループごとに調整する。
例えば、Webブラウザアプリを作成する場合、タブまたはウィンドウごとに1つのセッションを作成する。
あるいは、インタラクティブなセッションと別に、バックグラウンドダウンロードのためのセッションを作成する場合もある。
アプリは各セッションに、特定のURLリクエストを表す一連のタスクを追加する。
詳細は、URLSession
の開発者向けドキュメントを参照。
data(from:)メソッド
data(from:)
メソッドは、指定されたURLにコンテンツを探しに行って、レスポンスとデータを非同期的に取得する。
詳細はこちらのドキュメントを参照。
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: 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
構造体を定義する。
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
型として受け取る。
struct SearchResponse: Codable {
let results: [StoreItem]
}
JSONDecoderを利用する
JSONDecoder
は、JSONオブジェクトからデータ型のインスタンスをデコードできる。
ただし、JSONDecoder
インスタンスを使用してデコードを行うには、型がCodableに準拠すること。
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のデコードに失敗した場合は、それに起因するエラーをスローする。
func decode<T>(
_ type: T.Type,
from data: Data
) throws -> T where T : Decodable
パラメータ
-
type
JSONオブジェクトをデコードした結果となる型 -
data
デコードしたいJSOオブジェクト。
返り値
デコーダーがdata
を解析できた場合、type
パラメータに指定した型の値を返す。
フェッチ機能を抽象化する
リクエストの作成、レスポンスの解析、デコードといったネットワーキング・コードを抽象化するために、フェッチ関数を定義する。
この新しいfetchItems()
関数は、クエリ辞書を受け取り、StoreItems
の配列を返す。
非同期的な通信とエラー対処のため、宣言にはasync
とthrows
をマークする。
func tetchItems(
matching query: [String: String]
) async throws -> [StoreItem]
この関数はクエリを受け取って、URLを作成する。
そして、リクエストが完了すると、ステータスコードをチェックした上で、JSONをモデルオブジェクトにデコードする。
具体的には、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)
}
}