はじめに
動作環境は Xcode7.x, Swift2.x となります。
職業柄、入門者に教えることが多いのですが、APIを触るのは少しハードルが高いのかなと感じ、チュートリアルを書くに至りました。
今回はQiitaのAPIを叩き、最新記事一覧を取得・表示することを通して、
AlamofireとSwiftyJSONの仕様・使い方を紹介したいと思います。
以下は完成イメージです。
それではアプリを作る前に、まずはAPIについて簡単に説明をしてから始めます。
APIは怖くない
APIと聞くとなんだか凄いものと思う方がいるかもしれません。
ですがAPIはあなたが思っているほど難しいものではありません。
APIをとても簡単に説明すると 指定のURLにアクセスすると、配列や辞書型のようなデータを返してくれるもの です。
QiitaのAPIを例に見てみましょう。
https://qiita.com/api/v2/items
こちらのURLにアクセスすると、Qiitaの最新記事一覧が取得できます。
実際にブラウザで開いてみるとこのようなページが開きます。
何やら文字が羅列されていますが、これらが記事一覧のデータです。
少し見づらいので、これを単純な形で表現してみると以下のようになります。
[
{ title: "AlamofireとSwiftyJSONで〜",
body: "はじめに〜",
created_at: "2015-12-03 22:17:32"
},
{ title: "Optionalを〜",
body: "この記事は〜",
created_at: "2015-12-04 11:41:26"
}
]
まず、一番外側に [ ]
がありますね。
これは 配列 です。記事の一覧がこの配列に入っています。
そしてその中にある { title: "AlamofireとSwiftyJSONで〜", ... }
は 辞書型 です。
1つの辞書型が1つの記事を表しています。
Swiftの辞書型とは少し形が異なりますが、
{ title: "タイトル" }
という辞書型があった場合、
title
がキー、"タイトル"
が値となります。
ちなみにこのような表現形式を JSON と言います。
つまりAPIはJSONを返していたんですね。
配列と辞書型のようなものが返ってくると分かればSwiftでも扱えそうですよね!
ということでここからは実際にプロジェクトをいきましょう。
プロジェクト作成からライブラリの導入
プロジェクト作成
Single View Applicationで新規プロジェクトを作成します。
今回のプロジェクト名は QiitaViewer としましょう。
※ ターミナルの操作がよく分からない方はここでプロジェクトをデスクトップに保存してください。
ライブラリ導入
プロジェクトが出来たらライブラリを導入します。
今回はCarthage(カーセッジ)というライブラリ管理ツールを使用しましょう。
ではまずターミナルを開いてください。
Homebrewの導入
Homebrewを入れてない方は以下のコマンドを実行してインストールしましょう。
ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
Carthageの導入
次に先ほどインストールしたHomebrew経由でCarthageをインストールします。
以下のコマンドを実行しましょう。
brew install carthage
これでCarthage自体を使用する準備が出来ました。
ライブラリの導入
まずはライブラリを導入したいプロジェクトのところまで移動します。
先ほどデスクトップにプロジェクトを保存した方は以下のコマンドで移動できます。
cd ~/Desktop/QiitaViewer
ここで導入したいライブラリを記述するファイルを作ります。
以下のコマンドを実行してください。
touch Cartfile
作成が出来たらファイルを編集するので以下のコマンドを実行してください。
vim Cartfile
ターミナル上からCartfileを以下のように編集し、保存してください。
github "Alamofire/Alamofire"
github "SwiftyJSON/SwiftyJSON"
念のため編集方法も書いておきます。
i
を押すと入力モードになります。
入力モードから抜ける場合はesc
を押します。
その状態で:wq
を実行すると、保存して終了が出来ます。
保存したくない場合は:q!
で保存せず終了もできます。
編集が出来たら以下のコマンドからライブラリをビルドします。
carthage update --platform iOS
少し時間がかかりますが、完了すると以下のようになります。
次にプロジェクトの方でライブラリを追加する作業を行います。
すでにプロジェクトを開いている方は一度再起動してください。
まずプロジェクトファイルのGeneralタブから、
「Linked Frameworks and Library」にある+ボタンを押します。
Frameworksを選択する画面になるので、「Add Other...」を選択。
プロジェクトのディレクトリ内から、Carthage/Build/iOSと移動し、
ライブラリのAlamofire.frameworkとSwiftyJSON.frameworkを選択します。
ライブラリが追加できたら、Build Phasesタブに移動し、
+ボタンから、「New Run Script Phase」を選択します。
すると「Run Script」が現れます。
ここでまず「Shell」の下にある黒い部分に以下のコマンドを記述します。
/usr/local/bin/carthage copy-frameworks
そして次に「input Files」にて+ボタンを押し、以下のようにframeworkの情報を記述します。
$(SRCROOT)/Carthage/Build/iOS/Alamofire.framework
$(SRCROOT)/Carthage/Build/iOS/SwiftyJSON.framework
これでライブラリの追加は完了です。
次は画面を作成していきましょう!
記事一覧を表示する画面の作成
まず、最初からあるViewController.swiftは消します。
そして新たに ArticleListViewController.swift を作りましょう。
Storyboardの方ではArticleListViewControllerをInitial View Controllerにし、
Editor > Embed InからNavigation Controllerを選択します。
すると以下のようになるかと思います。
さて、ここからはArticleListViewControllerでコードを書いていきます。
はじめに不要な行を消し、ファイルを以下のような状態にしておきましょう。
import UIKit
class ArticleListViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
}
ここで記事の一覧を表示するTableViewを作っていきます。
class ArticleListViewController: UIViewController {
let table = UITableView() // プロパティにtableを追加
override func viewDidLoad() {
super.viewDidLoad()
table.frame = view.frame // tableの大きさをviewの大きさに合わせる
view.addSubview(table) // viewにtableを乗せる
}
}
ついでにNavigation Barに文字も入れておきましょう。
override func viewDidLoad() {
super.viewDidLoad()
title = "新着記事" // Navigation Barのタイトルを設定
セルについてはまだですが、これでレイアウトは完成です。
次はAlamofireを利用して記事の一覧を取得します!
記事の一覧を取得
ここではAlamofireを使用してAPIを叩き、記事の一覧を取得していきます。
まずはファイルでAlamofireを使うためにインポートしましょう。
import UIKit
import Alamofire // Alamofireをimport
記事の取得に関する処理はgetArticle
メソッドを定義し、ここに書いていきます。
func getArticles() {
}
まずはQiitaの新着記事を配信するURLへリクエストを送信します。
ここで使うのがAlamofireのrequest
メソッドです。
request
メソッドはmethod
とURLString
の、2つの引数を取ります。
URLString
は URLStringConvertible に準拠したものとなっていますが ここは単純にURLをString型で書けば大丈夫 です。
Method型の引数method
は 通信の種類 を表す引数です。
基本的な通信の種類はGET
とPOST
です。
この2つを簡単に紹介すると、以下のような使い分けがされます。
- 一方的に情報を受け取るときは
GET
- こちらが何か情報を送ってそれによって返される情報が変わるときは
POST
ここはAPIの使い方で書かれているはずなので、迷うことはありません。
問題はこの引数が Method型である というところです。
AlamofireにおいてMethod型は以下のようにenum(列挙型)で定義がされています。
そのため、この引数は.GET
のようにこのうちのいずれかのケースを書く必要があります。
今回アクセスするURLは https://qiita.com/api/v2/items で、
メソッドは GET です。
この情報から引数を埋めると以下のようになります。
func getArticles() {
Alamofire.request(.GET, "https://qiita.com/api/v2/items") // APIへリクエストを送信
}
さて、ここまででリクエストを送ることが出来たので、次は返ってきた情報を取得しましょう。
そのためには、先ほどのrequest
メソッドに続けて、responseJSON
メソッドを使用します。
responseJSON
メソッドは1つの関数を引数に取るので、その関数をクロージャで記述しましょう。
上の画像の通り、その関数では Response<AnyObject, NSError>型 の引数を使用できます。
ひとまずこの引数をresponse
という名前で扱うこととしましょう。
Alamofire.request(.GET, "https://qiita.com/api/v2/items")
.responseJSON { response in
// ここに処理を記述していく
}
さて、response
と定義したResponse型の引数は以下のような4つのプロパティを持ちます。
ですが記事の取得に使うのは、4番目のresult
だけです。
このresult
ですが、型は Result<Value, Error> となっていますね。
Result型の定義元を見てみるとenumで実装されているようです。
列挙型ResultはSuccessもしくはFailureを値に取り、
Successの場合にはValueを持っていることが分かります。
そしてこのValueこそが私たちの求める記事の情報なのです!
AlamofireはこのValueを取得するためにvalue
というプロパティを用意してくれています。
ではひとまず記事の情報を取得してprint
で表示してみるとしましょう。
コードは以下のようになります。
Alamofire.request(.GET, "https://qiita.com/api/v2/items")
.responseJSON { response in
print(response.result.value) // responseのresultプロパティのvalueプロパティをコンソールに出力
}
ここでviewDidLoad
メソッドの最後にgetArticle
メソッドの呼び出しを書いてから、実際にRunをしてみましょう。
override func viewDidLoad() {
// 省略
getArticles()
}
実行後、コンソールを見てみると何やらデータが表示されていることが分かります。
ここまでで記事一覧の情報を取得することが出来ました!
ですが、TableViewにこんなにたくさんの情報を表示は出来ないので、ここから表示したい情報だけを取得しましょう。
記事のデータから欲しい情報のみを取得
さて、TableViewに表示するために、必要なデータのみを取り出したいのですが、先ほど表示された記事一覧はAnyObject型となっていて扱うのが面倒です。
そこでSwiftyJSONの出番ですね。
とりあえずSwiftyJSONをインポートしておきましょう。
import UIKit
import Alamofire
import SwiftyJSON // SwiftyJSONをimport
SwiftyJSONはJSON型という、SwiftyJSONが定義した型にデータを変換してからそのデータを扱っていきます。
JSON型はAnyObject型からイニシャライズすることが出来るので、
先ほどのresponse.result.value
をアンラップして引数に渡してあげましょう。
Alamofire.request(.GET, "https://qiita.com/api/v2/items")
.responseJSON { response in
guard let object = response.result.value else {
return
}
let json = JSON(object)
}
guard文を使ってvalue
がnilだった場合は早期リターンをさせています。
if letを使ったときと比べ、ネストが深くならないのが良いですね。
さて、これでJSON型に変換出来たので、まずは記事の一覧を1つ1つの記事に分解していきましょう。
これにはforEach
メソッドを使用します。
forEach
メソッドはSwiftの標準ライブラリにあるSequenceTypeプロトコルに定義されています。
このメソッドは引数に1つの関数をとり、複数の要素それぞれに、引数で渡した処理をしてくれます。
そして JSON型はSequenceTypeプロトコルに準拠している ので、このメソッドが使えます。
実際に使用してみましょう。
クロージャを記述しようとすると、引数にはString型とJSON型とのタプルが使えることが分かります。
ここで、Stringにはその要素が何番目かという情報が入り、
JSONには記事1つのデータが入ります。
今回は記事が何番目か、という情報は使わないので 変数名を付けずアンダースコアにしておきます。
let json = JSON(object)
json.forEach { (_, json) in
// ここに処理を書いていく
}
それではここで記事のタイトルを取得してみましょう!
QiitaAPIのドキュメントによると、タイトルは"title"
というキーで取得出来るようです。
JSON型は辞書型と同じように[ ]
で特定のキーの値を取得することが出来ます。
let json = JSON(object)
json.forEach { (_, json) in
json["title"] // jsonから"title"がキーのものを取得
}
これはJSON型に subscript が実装されているからですね。
subscriptは配列や辞書型で扱うように、[ ]
をメソッドとして使えるようにするものです。
また、上の定義から分かるように、subscriptの返り値はJSON型となっています。
TableViewに表示するために、文字列はStringとして扱いたいので型の変換をしましょう。
そのためにはJSON構造体に定義されているstring
プロパティを使います。
string
プロパティはString型にキャスト出来ればそれを、出来なければnilを返すメソッドで、返り値はString?型となります。
これを使ってさらにprint
もしてみましょう。
let json = JSON(object)
json.forEach { (_, json) in
print(json["title"].string) // 記事タイトルを表示
}
以下のように記事のタイトルだけを取得することが出来ました!
それでは続いて投稿者のユーザーIDも取得しましょう。
再度QiitaAPIのドキュメントを見ると、投稿者のユーザーIDは、"user"
というキーの値の中の、"id"
というキーの値に格納されているようです。
つまり二重の辞書型に入ってる訳ですね!
これもSwiftyJSONで簡単に書けます。
let json = JSON(object)
json.forEach { (_, json) in
json["title"].string
print(json["user"]["id"].string) // 投稿者のユーザーIDを表示
}
何やらユーザーIDらしきものたちを表示することが出来ました。
さて、これで欲しい情報は全て取得できました!
記事の一覧をプロパティに保存
これらの情報をTableViewに表示するために、一度プロパティに保存しておきましょう。
まずはArticleListViewControllerにarticlesというプロパティを作ります。
型はキーがString、値がString?の配列としましょう。
class ArticleListViewController: UIViewController {
var articles: [[String: String?]] = [] // 記事を入れるプロパティを定義
次にforEach
メソッドの中でそれぞれの記事の情報をここに入れていきます。
また、保存の処理が終わったら全ての記事が保存出来たかを確認してみます。
let json = JSON(object)
json.forEach { (_, json) in
let article: [String: String?] = [
"title": json["title"].string,
"userId": json["user"]["id"].string
] // 1つの記事を表す辞書型を作る
self.articles.append(article) // 配列に入れる
}
print(self.articles) // 全ての記事が保存出来たら配列を確認
以下のように表示がされていれば正常に保存が出来ています!
記事の一覧を表示
最後に記事の表示をします。
まずArticleListTableViewに UITableViewDataSourceプロトコルを採用 しましょう。
class ArticleListViewController: UIViewController, UITableViewDataSource {
そしてtableView:numberOfRowsInSection:
メソッドとtableView:cellForRowAtIndexPath:
メソッドを定義していきます。
tableView:numberOfRowsInSection:
から。
表示したいセルの数は記事数と等しいのでarticles
のcount
プロパティを返しましょう。
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return articles.count
}
そしてtableView:cellForRowAtIndexPath:
ですね。
セルを作るのも面倒なので既存のスタイルのセルを使います。
その中でも今回はセルにタイトルとユーザーIDを表示したいので UITableViewCellStyle.Subtitle を使用します。
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: .Subtitle, reuseIdentifier: "cell") // Subtitleのあるセルを生成
return cell // cellを返す
}
続いてセルのtextLabel
、detailTextLabel
にそれぞれ、title
、userId
を表示するよう設定していきましょう。
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: .Subtitle, reuseIdentifier: "cell")
let article = articles[indexPath.row] // 行数番目の記事を取得
cell.textLabel?.text = article["title"]! // 記事のタイトルをtextLabelにセット
cell.detailTextLabel?.text = article["userId"]! // 投稿者のユーザーIDをdetailTextLabelにセット
return cell
}
これで UITableViewDataSourceに準拠できた ので、
table
のdataSource
プロパティにArticleListViewControllerを入れましょう。
table.frame = view.frame
view.addSubview(table)
table.dataSource = self // dataSouceプロパティに自身を代入
そして最後に、全ての記事が保存された段階でreloadData
メソッドを実行すれば記事の一覧を表示してくれます!
let json = JSON(object)
json.forEach { (_, json) in
let article: [String: String?] = [
"title": json["title"].string,
"userId": json["user"]["id"].string
]
self.articles.append(article)
}
self.table.reloadData() // TableViewを更新
このように表示されていれば完成です。
完成ソースコード
import UIKit
import Alamofire
import SwiftyJSON
class ArticleListViewController: UIViewController, UITableViewDataSource {
var articles: [[String: String?]] = []
let table = UITableView()
override func viewDidLoad() {
super.viewDidLoad()
title = "記事一覧"
table.frame = view.frame
view.addSubview(table)
table.dataSource = self
getArticles()
}
func getArticles() {
Alamofire.request(.GET, "https://qiita.com/api/v2/items")
.responseJSON { response in
guard let object = response.result.value else {
return
}
let json = JSON(object)
json.forEach { (_, json) in
let article: [String: String?] = [
"title": json["title"].string,
"userId": json["user"]["id"].string
]
self.articles.append(article)
}
self.table.reloadData()
}
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return articles.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: .Subtitle, reuseIdentifier: "cell")
let article = articles[indexPath.row]
cell.textLabel?.text = article["title"]!
cell.detailTextLabel?.text = article["userId"]!
return cell
}
}
最後に
今回はAPIを叩いてみる、ということを目的にコードを書いてきました。
そのため、責務ごとにファイルを分けるといったことは行っていません。
もし、そういったアーキテクチャ・パターンに興味のある方は これが最強のMVC(iOS) などを参考にすると良いでしょう。
参考資料
Alamofire - Github
SwiftyJSON - Github
Qiita API ドキュメント
Carthageを使ってビルド時間を短縮しよう