概要
iOSエンジニアの通信処理の練習用にこんなAPIを作成しました。
herokuで無料枠でデプロイしていますのでお金はかからないはずです。
その代わりにスリープモードなのでアクセスしてもレスポンスが遅いので、2,3回アクセスして起こしてもらう必要があります。
{"items":[{"id":1,"first_name":"Taro","last_name":"Tanaka","title":"first_data","create_at":"2018-12-30T15:03:01.012345"},{"id":2,"first_name":"Hanako","last_name":"Yamada","title":"second-data","create_at":"2018-12-31T18:11:01.012345"}]}
[{"id":1,"first_name":"Taro","last_name":"Tanaka","title":"first_data","create_at":"2018-12-30T15:03:01.012345"},{"id":2,"first_name":"Hanako","last_name":"Yamada","title":"second-data","create_at":"2018-12-31T18:11:01.012345"}]
{"id":1,"first_name":"Taro","last_name":"Tanaka","title":"first_data","create_at":"2018-12-30T15:03:01.012345"}
{"id":2,"first_name":"Hanako","last_name":"Yamada","title":"second-data","create_at":"2018-12-31T18:11:01.012345"}
上記のURLに接続するとJSONが出力されます。
つまり、こちらがAPIとなります。
これらをそれぞれネイティブ側(iOS)でパースしてみましょう。
環境
ライブラリ・環境 | バージョン |
---|---|
Xcode | 10.1 |
Swift | 4.2 |
Alamofire | 4.8.0 |
モデルクラスの設計と実装
今回はモデルクラスの命名をTask
にしました。
今回は全てをデコードするのではなくid
とfirst
とlast
をそれぞれデコードする。
Task.swift
struct Task: Codable {
let id: Int
let first: String
let last: String
enum CodingKeys: String, CodingKey {
case id
case first = "first_name"
case last = "last_name"
}
}
を使うとパラメータと変数名が一致するためCodingKeys
が必要なくなります。
こちらは私なりの配慮です。
easy版を使う場合
Task.swift
struct Task: Codable {
let id: Int
let first: String
let last: String
}
以上でモデルクラスの作成が完了します。
Controller側でAPIを呼び出してデコードする
ViewController.swift
import UIKit
import Alamofire
class ViewController: UIViewController {
private var items: [Task]?
override func viewDidLoad() {
super.viewDidLoad()
requestFirst()
requestItems()
}
private func requestFirst() {
Alamofire.request("https://todo-practice-app.herokuapp.com/first").response { response in
guard let data = response.data else {
return
}
let decoder = JSONDecoder()
do {
let task: Task = try decoder.decode(Task.self, from: data)
print(task)
} catch {
print(error)
}
}
}
private func requestItems() {
Alamofire.request("https://todo-practice-app.herokuapp.com/items").response { response in
guard let data = response.data else {
return
}
let decoder = JSONDecoder()
do {
let tasks: [Task] = try decoder.decode([Task].self, from: data)
print(tasks)
} catch {
print(error)
}
}
}
}
requestItems()
を呼び出し場合
[TodoApp.Task(id: 1, first: "Taro", last: "Tanaka"), TodoApp.Task(id: 2, first: "Hanako", last: "Yamada")]
requestFirst()
を呼び出し場合
MobileGestalt.c:890: MGIsDeviceOneOfType is not supported on this platform.
Task(id: 1, first: "Taro", last: "Tanaka")
さて、ここまではよくQiitaで見かける記事であります。
次にこれのModelのUnitTestを作ります。
ModelクラスのUnitTestを作成する
プロジェクトファイルの構成は下記の通りに実装します。
ResourcesフォルダにJSONのオブジェクトを入れる形式が好みです。
one_task.json
{
"id": 1,
"first_name": "Taro",
"last_name": "Tanaka",
"title": "first_data",
"create_at": "2018-12-30T15:03:01.012345"
}
テストを書きます。
TestTask.swift
import XCTest
@testable import TodoApp
class TestTask: XCTestCase {
func test_Task()
{
guard let jsonObject = createDataObject() else {
XCTFail("Dataの生成に失敗")
return
}
let decoder = JSONDecoder()
guard let task = try? decoder.decode(Task.self, from: jsonObject) else {
XCTFail("Taskの生成に失敗")
return
}
XCTAssertEqual(task.id, 1)
XCTAssertEqual(task.first, "Taro")
XCTAssertEqual(task.last, "Tanaka")
}
private func createDataObject() -> Data?
{
let testBundle = Bundle(for: type(of: self))
let path = testBundle.url(forResource: "one_task", withExtension: "json")
let data = try? Data(contentsOf: path!, options: .uncached)
return data
}
}
あとはユニットテストを実行してSuccess
が表示されたら完了です。
これで無事にMockからJSONDecoder
でCodableでデコードがしっかりできていることがわかりました。