##はじめに
当方、Swiftというかプログラミング全般の経験が浅く、特にJSONデータの取り扱いに対して苦手意識があったこと、そしてSwiftにおいてライブラリを使うことに対する恐怖心?があったので、備忘録的にこちらに説明を残しておきます。
##初心者による初心者のための記事
はい、タイトル通りです。なので、自分の理解している範囲で、コードになるべく多くの説明を入れております。分かる方にはちょっとうっとうしいかもですが、もし間違いなど発見されましたら、ぜひコメント欄にでも指摘いただければ嬉しいです。
##環境
Swift 5.2.4
Alamofire 5.2.1
SwiftyJSON 5.0.0
##ライブラリを使う
SwiftにはAlamofire、SwiftyJSONというJSONデータを取ってくるときに重宝されているライブラリがあるらしく、デフォルトのURLSessionを使ったやり方と並べて説明します。
##そもそもJSONって何?
こんなやつです。参照
[
{
"userId": 1,
"id": 1,
"title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
"body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
},
{
"userId": 1,
"id": 2,
"title": "qui est esse",
"body": "est rerum tempore vitae\nsequi sint nihil reprehenderit dolor beatae ea dolores neque\nfugiat blanditiis voluptate porro vel nihil molestiae ut reiciendis\nqui aperiam non debitis possimus qui neque nisi nulla"
}
...省略
]
Web上から情報を取ってきて(例えば天気予報とか)それを自分のアプリに表示したりするときに使われるデータの形で、うまく使えれば最強になれます。
でも、いろんなプログラミング言語に対応している分、送ったり受けたりする際にはデータの形を修正する必要があります。受け取る際には、ちゃんとそのデータに対応した形で受け取らないとうんともすんとも言ってくれません。
##完成版
最終的にこんな感じになります。
JSONで引っ張ってきたデータをTableViewのCellに一個ずつ表示してます。
##まずは取り扱うデータをclassにします
今回はこちらの練習用のJSONデータを使います。
ここでミスるとデータ取得できません。JSONデータがあるURLを実際に叩いて、それを見ながら丁寧に作りましょう。
今回の場合、データがシンプルなのでコードもめっちゃシンプルですが、構造が複雑になる(ネストしたJSONだ)ともっとclassを書かなきゃいけません。ここでは割愛しますが、今度記事書くつもりです。
import Foundation
class TestData: Decodable {
var id: Int?
var title: String?
var body: String?
init(id: Int, title: String, body: String) {
self.id = id
self.title = title
self.body = body
}
}
URLSessionでのデータ取得
それではUIKitに最初から入っているURLSessionでJSONデータを取得します。
コメントなしバージョンはGitHubをご覧ください。
import UIKit
class ViewController: UIViewController {
@IBOutlet weak var tableView: UITableView!
var dataArray = [TestData]()
//Webからデータを取得して諸々の処理をしたあと、ここにデータが埋まる。
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.delegate = self
self.tableView.dataSource = self
//この二つはtableViewとViewControllerを諸々繋いでいるところ。
//Storyboardでつなげておけば記載の必要はない
getData() //viewが最初にロードされる時に1度だけgetData関数が実行される
}
func getData(){
let urlString = "https://jsonplaceholder.typicode.com/posts"
//データ取得をしたいurlを変数にいれる(現状urlStringは文字列なのでデータ取得には使えない)
guard let url = URL(string: urlString) else {return}
//上記のurlStringをURL型に変換とアンラップ(空っぽじゃないことを確かめて使える状態にする)
//Swiftではデータが空っぽ(=nil)だとアプリが止まっちゃう
let task = URLSession.shared.dataTask(with: url) { [weak self](data, response, error) in
//先ほど取得したurlで諸々作業しますよーとURLSession.shared.dataTask(with: url)が言ってます
//すると3種類のデータが返ってきます(以降の処理のためにそれぞれdata, response, errorと名前をつけています)
if let error = error {
print(error)
return
}
//3番目のデータ(=error)が返ってきているとその時点でreturnし(処理を止め)ます
guard let data = data else {return}
//1番目のデータがurlに書かれている僕たちが欲しいデータです。
//そちらが空っぽじゃないかを確認して、空っぽだとreturnします。
//このデータこそJSONなのですが、このままだとSwiftで使えないので使える型に直す(=Decode)しなければならない
let decoder = JSONDecoder()
//Decodeの機能を持ったJSONDecoderを使うためにここではdecoderという名前の変数を作りそこにJSONDecoderを入れています。
let tempArray = try? decoder.decode([TestData].self, from: data)
//tryで文字通りJSONであるdataをTestDataの配列の形に変えています。
//失敗したらそこで処理が止まります。成功すればTestDataの配列がtempArrayに入る
guard let unwrappedArray = tempArray else {return}
//tempArrayが空っぽでないことを確認し
self?.dataArray = unwrappedArray
//上の方で作っていたdataArrayに代入
DispatchQueue.main.async {
//UIの処理はDispatchQueue.main.async内で行う(詳しくは今度記事書きたい)
////データ取得後tableViewをリロード。しないと真っ白のまま
self?.tableView.reloadData()
}
}
task.resume() //これがないと一切動きません。エラーも出ないので要注意。
}
}
//以下はtableViewの基本的な処理なので割愛。
//extensionを使って別場所に書くことで大元のViewControllerをすっきりさせてる。
extension ViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return dataArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = dataArray[indexPath.row].title
return cell
}
}
decodeしないといけないのがめんどくさい気もしますが、初心者にはまずこちらでデータ取得の際に何がどうなってるかが見えやすいこちらがおすすめかなと思いました。
AlamofireとSwiftyJSONでのデータ取得
次にAlamofireとSwiftyJSONというライブラリ(どこぞの天才が作ってくれたいろいろすごい機能を自分のアプリでも使えるようになるやつ)を使って同じ処理をしてみたいと思います。
CocoaPodsをインストール
詳しくはこちら
##CocoaPodsでAlamofireとSwiftyJSONをインストール
Podfileに下記を記載し、pod install
pod'Alamofire'
pod'SwiftyJSON'
インストールが終わったら〇〇.workspaceというファイルができてるのでそちらを開く。
実際のコード
先ほどTestData.swiftのTestDataクラスにひっついていたDecodableはこちらだと不要です。SwiftyJSON様が後ほどdecodeやってくれます。
class TestData: Decodable {
...
↓
class TestData {
...
コメントが多く見づらい場合はGitHubを参照してください。
URLSession編と同じ箇所の説明は割愛してます。
import UIKit
import Alamofire //ライブラリを使えるようにする
import SwiftyJSON //同上
class ViewController: UIViewController {
@IBOutlet weak var tableView: UITableView!
var dataArray = [TestData]()
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.delegate = self
self.tableView.dataSource = self
getData()
}
func getData(){
let urlString = "https://jsonplaceholder.typicode.com/posts"
guard let url = URL(string: urlString) else {return}
AF.request(url).responseJSON { [weak self](response) in
//AF.request().responseJSONの箇所がAlamofireの書き方
//先ほど取得したurlに対してリクエストを投げる
//リクエスト==そのurlにあるデータくださいってお願いするとJSON形式でデータが返ってくる
//返ってきたものにresponseという名前をつけて、以降の処理で使えるようにする
//[weak self]はメモリーリークを避けるため(後日、記事書きます)
let data = JSON(response.value as Any)
//JSON()がSwiftyJSONの関数
//上で説明したresponseの値を.valueで取得
//それをJSON関数に入れることでDecode(つまりSwiftでも扱える型に直)してくれる
let tempArray = data.arrayValue
//JSONからDecodeされたデータは配列の形に直してtempArrayという変数に入れてる
for element in tempArray{
//tempArrayはまだJSONデータの配列なので一つ一つを取り出し処理してdecodeする
let id = element["id"].intValue
let title = element["title"].stringValue
let body = element["body"].stringValue
//elementも配列(TestData.swiftで作ったclass)なので配列名[key名]でアクセス
//そこに.intValueで数字に、.stringValueで文字列に変換
let data = TestData(id: id, title: title, body: body)
//取得した3つのデータを使って、1つのTestDataを作成
self?.dataArray.append(data)
//それを上の方で作成したdataArrayに入れ込む。この工程をtempArray全ての要素に適用。
//最終的にtempArrayに入っていたデータが全てTestDataの形となりdataArrayに入っている状態になる。
}
DispatchQueue.main.async {
self?.tableView.reloadData()
self?.view.backgroundColor = .systemBlue
//ついでに背景を青にしてみる
}
}
}
}
extension ViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return dataArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = dataArray[indexPath.row].title
return cell
}
}
##比較してみて
いろいろ説明を入れたの見づらいのと、コードがやたら長ーーく見えます、すみません。
ただデータを引っ張ってきてるだけなので、コード自体の長さや複雑さに両者に変わりはほぼありませんが、多分規模が大きくなったら違うのかなと思います。
SwiftyJSONを使うとわざわざdecodeしなくてよかったりするので、すっきりした印象です。
##Alamofireについて
当方Youtubeでいろいろやり方を学ぶことが多いのですが、見ていると、Alamofireの方はいろいろ書き方があって(コールバックがどうのとか?)上記の書き方はまだまだ甘いかもです。引き続き勉強します。
##今後書きたいこと(自分用)
・[weak self]について
・Dispatchqueue.main.asyncについて
・ネストされたJSONの場合のクラスの書き方について
・コールバックについて
##参考
Youtube
Alamofire
SwiftyJSON
JSONPlaceholder
##最後に
ここまで読んでいただきありがとうございました。日頃からqiitaの記事に助けられることが多いのですが、今回初めて自分で書いてみて、記事書く方も大変なんだなと実感しました。これからもちょくちょくアウトプットの場としていろいろ書いていけたらと思いますので、よろしくお願いいたします。