#9 第3課 連接網路資料庫

在第2單元第2-7課曾提過,若程式學會解碼JSON格式,就像多出一隻手延伸到網路上的資料庫,可源源不絕取得資料更新網頁,本課就來實現這個想法。
當然,前提是網路上的伺服器必須開放權限,允許取用資料庫才行。
Apple公司的 iTunes 資料庫就是一個對任何人開放的音樂資料庫,可以傳回 JSON 格式的資料,再用程式加以解碼,就可獲得全世界音樂藝人的相關資料。例如,用瀏覽器連接網址:
https://itunes.apple.com/search?term=Justin+Bieber&media=music
就會在 iTunes 資料庫搜尋小賈斯汀 "Justin Bieber",傳回一個 JSON 格式的檔案,共有50筆資料,內容如下:
{
"resultCount":50,
"results": [
{
"wrapperType":"track",
"kind":"song",
"artistId":259760619,
"collectionId":359966550,
"trackId":359966562,
"artistName":"Sean Kingston & Justin Bieber",
"collectionName":"Eenie Meenie - Single",
"trackName":"Eenie Meenie",
"collectionCensoredName":"Eenie Meenie - Single",
"trackCensoredName":"Eenie Meenie",
"artistViewUrl":"https://music.apple.com/us/artist/sean-kingston/259760619?uo=4",
"collectionViewUrl":"https://music.apple.com/us/album/eenie-meenie/359966550?i=359966562&uo=4",
"trackViewUrl":"https://music.apple.com/us/album/eenie-meenie/359966550?i=359966562&uo=4",
"previewUrl":"https://audio-ssl.itunes.apple.com/itunes-assets/AudioPreview125/v4/bb/ce/30/bbce3088-f26c-f5e9-b8af-50344243726e/mzaf_17828995580283253999.plus.aac.p.m4a",
"artworkUrl30":"https://is3-ssl.mzstatic.com/image/thumb/Music125/v4/56/f9/aa/56f9aa10-f1f3-dd77-76d4-2d979030b6fd/source/30x30bb.jpg",
"artworkUrl60":"https://is3-ssl.mzstatic.com/image/thumb/Music125/v4/56/f9/aa/56f9aa10-f1f3-dd77-76d4-2d979030b6fd/source/60x60bb.jpg",
"artworkUrl100":"https://is3-ssl.mzstatic.com/image/thumb/Music125/v4/56/f9/aa/56f9aa10-f1f3-dd77-76d4-2d979030b6fd/source/100x100bb.jpg",
"collectionPrice":1.29,
"trackPrice":1.29,
"releaseDate":"2010-03-19T07:00:00Z",
"collectionExplicitness":"notExplicit",
"trackExplicitness":"notExplicit",
"discCount":1,
"discNumber":1,
"trackCount":1,
"trackNumber":1,
"trackTimeMillis":201880,
"country":"USA",
"currency":"USD",
"primaryGenreName":"Pop",
"isStreamable":true},
....<省略>
]
}
想要寫JSON程式,首先必須解析資料的欄位結構。還記得2-7課提到,在JSON格式中,大括號 { } 裡面是一個物件實例,物件實例的每個欄位為 key: value 的形式,欄位之間或陣列元素之間以逗號隔開。陣列則包含在中括號 [ ] 之中。
所以根據上面回傳的JSON內容,有兩層結構,外層是大的物件實例,只有兩個欄位:
struct 頁面結構 {
let resultCount: Int
let results: [單項]
}
第二個欄位 results 是一個陣列,陣列元素「單項」則是另一層資料結構,有31個欄位。根據這樣的欄位解析,就可以來寫一個傑森解碼器,透過網路抓取 iTunes 資料庫。程式範例如下:
// 3-3a JSON by URLSession
// Created by Heman, 2021/09/03
// Updated by Heman, 2023/10/03
// https://developer.apple.com/library/archive/documentation/AudioVideo/Conceptual/iTuneSearchAPI/index.html
import PlaygroundSupport
import SwiftUI
let 網址 = "https://itunes.apple.com/search?term=Justin+Bieber&media=music"
struct 頁面結構: Codable {
let resultCount: Int
let results: [單項]
}
struct 單項: Codable, Hashable {
let wrapperType: String //track, collection, artist
let kind: String //book, album, coached-audio, feature-movie,
//interactive-booklet, music-video, pdf podcast,
//podcast-episode, software-package,
//song, tv-episode, artist
let artistId: Int
let collectionId: Int
let trackId: Int
let artistName: String
let collectionName:String
let trackName:String
let collectionCensoredName: String
let trackCensoredName: String
let artistViewUrl: URL
let collectionViewUrl: URL
let trackViewUrl: URL
let previewUrl: URL?
let artworkUrl30: URL?
let artworkUrl60: URL?
let artworkUrl100: URL?
let collectionPrice: Double?
let trackPrice: Double?
let releaseDate: String
let collectionExplicitness: String
let trackExplicitness: String
let discCount: Int
let discNumber: Int
let trackCount: Int
let trackNumber: Int
let trackTimeMillis: Int?
let country: String
let currency: String
let primaryGenreName: String
let isStreamable: Bool?
}
struct 更新頁面: View {
@State var 歌曲列表: [單項]?
func 更新歌曲列表() {
guard let myURL = URL(string: 網址) else { return }
URLSession.shared.dataTask(with: myURL) { 回傳資料, 回傳碼 , 錯誤碼 in
if let 解碼資料 = 回傳資料 {
do {
let 解碼結果 = try JSONDecoder().decode(頁面結構.self, from: 解碼資料)
print(回傳碼 ?? "No response")
歌曲列表 = 解碼結果.results
} catch {
print("JSON解碼錯誤")
}
} else {
print(錯誤碼 ?? "No error")
}
}.resume()
}
var body: some View {
if 歌曲列表 == nil {
ProgressView()
.onAppear {
更新歌曲列表()
}
} else {
List(歌曲列表!, id: \.self) { 歌曲 in
Label(歌曲.trackName, systemImage: "music.note")
.font(.title)
.lineLimit(1)
}
}
}
}
PlaygroundPage.current.setLiveView(更新頁面())
本範例如果和上一課3-2c程式比較,會發現視圖結構非常類似,狀態變數的類型,從圖片 UIImage? 改成資料結構的陣列 [單項]?,也就是:
//3-2c
@State var 下載圖片: UIImage?
換成
//3-3a
@State var 歌曲列表: [單項]?
同樣是透過狀態變數來控制畫面的更新,網路下載的內容先導入傑森解碼器 JSONDecoder() 解碼,再將結果的陣列內容指定給狀態變數「歌曲列表」。網路連線的程式碼如下:
func 更新歌曲列表() {
guard let myURL = URL(string: 網址) else { return }
URLSession.shared.dataTask(with: myURL) { 回傳資料, 回傳碼 , 錯誤碼 in
if let 解碼資料 = 回傳資料 {
do {
let 解碼結果 = try JSONDecoder().decode(頁面結構.self, from: 解碼資料)
print(回傳碼 ?? "No response")
歌曲列表 = 解碼結果.results
} catch {
print("JSON解碼錯誤")
}
} else {
print(錯誤碼 ?? "No error")
}
}.resume()
}
視圖主體(body)部分也跟 3-2c 差不多,同樣先用 ProgressView() 顯示下載中,等下載完資料並經過傑森解碼器解碼,陣列「歌曲列表」就用 List() 排版,顯示50筆歌曲名稱(trackName)。List 用法請參考第2單元2-9課。
var body: some View {
if 歌曲列表 == nil {
ProgressView()
.onAppear {
更新歌曲列表()
}
} else {
List(歌曲列表!, id: \.self) { 歌曲 in
Label(歌曲.trackName, systemImage: "music.note")
.font(.title)
.lineLimit(1)
}
}
}
執行結果如下,記得先將Swift Playgrounds的「啟用結果」關閉再執行。

註解
- iTunes 資料庫的31個欄位有些是可以省略的(設為Optional),每個欄位的涵義可參考Apple 原廠文件。
- 根據上述原廠文件,搜尋字串的空格,需改為加號 + ,因此網址的寫法:
https://itunes.apple.com/search?term=Justin+Bieber&media=music
- 根據 JSONDecoder() 的需要,資料結構需符合 Codable 規範,而在 List() 使用上,需要符合 Hashable 規範,反映在資料結構的定義上:
struct 頁面結構: Codable { }
struct 單項: Codable, Hashable { }