將網路抓下來的資料放在app中

先將它變成 array

解析 JSON 資料的兩個方法

  • 利用 JSONDecoder
  • 利用 JSONSerialization (舊的方法)

方法一、利用 JSONDecoder & Codable 解析 JSON

遵從 protocol Codable 的型別 資料可以解碼(Decodable)和編碼(Encodable)

  • Decodable
    • 可以解碼(從 Data 型別轉換)
    • 可以讀檔
  • Encodable
    • 可以編碼(轉換成 Data 型別)
    • 可以存檔

基本型別:

  • Int
  • String
  • Bool
  • Double, Float
  • Data
  • Date
  • URL
  • Array
  • Dictionary

遵從 protocol Codable 的型別:

struct Lover: Codable  {
    let name: String
    let star: String
    let innerBeauty: Bool
}

只要屬性都有遵從 protocol Codable,Swift 可以自動幫我們 寫出解碼編碼的程式,因此我們不用定義 Codable 的 function

如果只要解碼,可以只遵從 Decodable protocol
如果只要編碼,可以只遵從 Encodable protocol

也可以用 class 或 enum 遵從 protocol Codable

沒遵從 protocol Codable 的屬性,我們必須自己定義 Codable 的 function,如:UIButton

如何從 JSON 資料定義遵從 Codable protocol 的型別

官方文件

範例實作 - 隨機的 meme 圖片

API 的網址如下

https://some-random-api.ml/meme

格式如下:


{
"id": 12,
"image": "https://i.some-random-api.ml/07jjUxQKNf.png",
"caption": "Ooof",
"category": "random"
}

定義型別:mene

測試程式時,可以先用playground測試

利用 JSONDecoder,我們可以將網路上抓下來的 JSON 資料從 Data 型別變成自訂型別 Meme。

將 JSON 裡以 { } 描述的 object 變成自訂型別,可以用 class,也可以用 struct 定義,型別名字可自取。

struct mame: Codable {
    let id: Int
    let image: URL
    let caption: String
}

遵從 protocol Codable 後,Meme 將有解碼跟編碼的功能。

JSON 裡 object 的 key 將成為自訂型別的 property 名字, 而 property 的型別則由 key 對應的 value 型別決定。

property 可宣告為變數,也可宣告為常數。

以下幾點規則決定 property 的型別:

  • 網址 ➪ URL。(如果網址包含 ASCII 以外的文字,請將型別宣告為 String,之後再另外轉成 URL)
  • 時間 ➪ Date。
  • 整數 ➪ Int
  • 浮點數 ➪ Float 或 Double
  • 字串 ➪ String
  • true 或 false ➪ Bool
  • 陣列 ➪ Array
  • 若value 是 object ➪ 遵從 protocol Decodable 或 Codable 的自訂型別。
  • key 不一定會出現或 key 的 value 可能為 null 時 ➪ 型別要宣告為 optional。

習慣用 extension 遵從 protocol 的朋友,也可以改用以下寫法:

struct Meme {
   let id: Int
   let image: URL
   let caption: String
   let category: String
}
extension Meme: Codable { }

值得注意的,只要型別不對就會失敗,就算加問號也一樣,因此 let id: String? 也會轉換失敗。

以下兩種情況 property 要宣告為 optional:

JSON object 不一定有的 key 。
JSON object 某個 key 對應的 value 有可能是 null。

測試 JSON 解碼是否成功

方法 1: 使用 async & await

struct Meme: Codable {
    let id: Int
    let image: URL
    let caption: String
    let category: String
}
let urlString = "https://some-random-api.ml/meme"
if let url = URL(string: urlString) {
    Task {
        do {
            let (data, _) = try await URLSession.shared.data(from: url)
            let decoder = JSONDecoder()
            let meme = try decoder.decode(Meme.self, from: data)
            print(meme)
        } catch {
            print(error)
        }
    }
}

......

Meme(id: 7, image: https://i.some-random-api.ml/9sBO7cjaDe.png, caption: "Google it please...", category: "random")

方法 2: 使用 completion handler

struct Meme: Codable {
    let id: Int
    let image: URL
    let caption: String
    let category: String
}
let urlString = "https://some-random-api.ml/meme"
if let url = URL(string: urlString) {
    URLSession.shared.dataTask(with: url) { data, response , error in
        if let data {
            let decoder = JSONDecoder()
            do {
                let meme = try decoder.decode(Meme.self, from: data)
                print(meme)
            } catch {
                print(error)
            }
        }
    }.resume()
}

//或是

struct Meme: Codable {
    let id: Int
    let image: URL
    let caption: String
    let category: String
}

let url = URL(string: "https://some-random-api.ml/meme")!

URLSession.shared.dataTask(with: url){
    data, response, error
    in
    if let data {
        let decoder = JSONDecoder()
        do {
            let meme = try decoder.decode(Meme.self, from: data)
            print(meme)
        } catch  {
            print(error)
        }
    }
}.resume()

Meme 資料放到 cell 上

新增一個檔案

新增二個元件,一個圖片一個文字,並拉線命名。

安裝Package - kingfisher

方便抓取網路上的圖片

完成

因為要引用套件

記得要 import Kingfisher

新增檔案來放新增的型別

檔名:File

並把剛剛的型別命名貼上

把程式放到 ViewController.swift

主程式:


/
//  ViewController.swift
//  0927jsonDemo
//
//  Created by Huang on 2022/9/27.
//

import UIKit
import Kingfisher

class ViewController: UIViewController {

    @IBOutlet weak var imageView: UIImageView!
    @IBOutlet weak var label: UILabel!
    
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        
        let url = URL(string: "https://some-random-api.ml/meme")!
        
        URLSession.shared.dataTask(with: url){
            data, response, error
            in
            if let data {
                let decoder = JSONDecoder()
                do {
                    let meme = try decoder.decode(Meme.self, from: data)
                } catch  {
                    print(error)
                }
            }
        }.resume()
    }


}

在閉包裡面,如果要讀到外面的型別,就要加 self

把文字放到label:

self.label.text = meme.caption

把圖片放到 image

self.imageView.kf.setImage(with: meme.image)

上面兩行放在 do 裡面


do {
    let meme = try decoder.decode(Meme.self, from: data)
    print(meme.caption)
    //文字
    self.label.text = meme.caption
    //圖片
    self.imageView.kf.setImage(with: meme.image)

} catch  {
    print(error)
}

啟動模擬手機

打開後圖片跟文字都會顯示出來,但是可能會比較慢,並且,會出現紫色的提示訊息。

再改一下程式碼:

do {
    let meme = try decoder.decode(Meme.self, from: data)
    print(meme.caption)
    
    DispatchQueue.main.sync {
        //文字
        self.label.text = meme.caption
        //圖片
        self.imageView.kf.setImage(with: meme.image)
    }
} catch  {
    print(error)
}

程式改完後,下載的速度快了些,紫色訊息也不見了。

0927JSON解析練習檔案-meme梗圖抓取