AlamofireとSwiftyJSONでAPIを叩くチュートリアル

  • 430
    Like
  • 12
    Comment

はじめに

動作環境は Xcode7.x, Swift2.x となります。

職業柄、入門者に教えることが多いのですが、APIを触るのは少しハードルが高いのかなと感じ、チュートリアルを書くに至りました。

今回はQiitaのAPIを叩き、最新記事一覧を取得・表示することを通して、
AlamofireとSwiftyJSONの仕様・使い方を紹介したいと思います。

以下は完成イメージです。

iPhone_5s_-_iPhone_5s___iOS_9_1__13B137_.png

それではアプリを作る前に、まずはAPIについて簡単に説明をしてから始めます。

APIは怖くない

APIと聞くとなんだか凄いものと思う方がいるかもしれません。
ですがAPIはあなたが思っているほど難しいものではありません。

APIをとても簡単に説明すると 指定のURLにアクセスすると、配列や辞書型のようなデータを返してくれるもの です。

 
QiitaのAPIを例に見てみましょう。

https://qiita.com/api/v2/items
こちらのURLにアクセスすると、Qiitaの最新記事一覧が取得できます。

実際にブラウザで開いてみるとこのようなページが開きます。

https___qiita_com_api_v2_items.png

何やら文字が羅列されていますが、これらが記事一覧のデータです。

 
少し見づらいので、これを単純な形で表現してみると以下のようになります。

[
  { 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

少し時間がかかりますが、完了すると以下のようになります。

1__bash.png

 
次にプロジェクトの方でライブラリを追加する作業を行います。
すでにプロジェクトを開いている方は一度再起動してください。

まずプロジェクトファイルのGeneralタブから、
「Linked Frameworks and Library」にある+ボタンを押します。

QiitaViewer_xcodeproj_と_Swift_-_Carthageを使ってビルド時間を短縮しよう_-_Qiita_と_AlamofireとSwiftyJSONでAPIを叩くチュートリアル.png

Frameworksを選択する画面になるので、「Add Other...」を選択。

QiitaViewer_xcodeproj.png

プロジェクトのディレクトリ内から、Carthage/Build/iOSと移動し、
ライブラリのAlamofire.frameworkとSwiftyJSON.frameworkを選択します。

QiitaViewer_xcodeproj.png

ライブラリが追加できたら、Build Phasesタブに移動し、
+ボタンから、「New Run Script Phase」を選択します。

OtherViews_と_QiitaViewer_xcodeproj.png

すると「Run Script」が現れます。
ここでまず「Shell」の下にある黒い部分に以下のコマンドを記述します。

/usr/local/bin/carthage copy-frameworks

そして次に「input Files」にて+ボタンを押し、以下のようにframeworkの情報を記述します。

$(SRCROOT)/Carthage/Build/iOS/Alamofire.framework
$(SRCROOT)/Carthage/Build/iOS/SwiftyJSON.framework

QiitaViewer_xcodeproj.png

これでライブラリの追加は完了です。

次は画面を作成していきましょう!

記事一覧を表示する画面の作成

まず、最初からあるViewController.swiftは消します。

そして新たに ArticleListViewController.swift を作りましょう。

ArticleListViewController_swift.png

Storyboardの方ではArticleListViewControllerをInitial View Controllerにし、
Editor > Embed InからNavigation Controllerを選択します。

すると以下のようになるかと思います。

Main_storyboard_—_Edited.png

 
さて、ここからはArticleListViewControllerでコードを書いていきます。

はじめに不要な行を消し、ファイルを以下のような状態にしておきましょう。

ArticleListViewController
import UIKit

class ArticleListViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

    }
}

ここで記事の一覧を表示するTableViewを作っていきます。

ArticleListViewController
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に文字も入れておきましょう。

ArticleListViewController
override func viewDidLoad() {
    super.viewDidLoad()
    title = "新着記事" // Navigation Barのタイトルを設定

セルについてはまだですが、これでレイアウトは完成です。

次はAlamofireを利用して記事の一覧を取得します!

記事の一覧を取得

ここではAlamofireを使用してAPIを叩き、記事の一覧を取得していきます。

まずはファイルでAlamofireを使うためにインポートしましょう。

ArticleListViewController
import UIKit
import Alamofire // Alamofireをimport

記事の取得に関する処理はgetArticleメソッドを定義し、ここに書いていきます。

ArticleListViewController
func getArticles() {
}

 
まずはQiitaの新着記事を配信するURLへリクエストを送信します。

ここで使うのがAlamofireのrequestメソッドです。

ArticleListViewController_swift_—_Edited.png

requestメソッドはmethodURLStringの、2つの引数を取ります。

URLStringURLStringConvertible に準拠したものとなっていますが ここは単純にURLをString型で書けば大丈夫 です。

Method型の引数method通信の種類 を表す引数です。
基本的な通信の種類はGETPOSTです。

この2つを簡単に紹介すると、以下のような使い分けがされます。

  • 一方的に情報を受け取るときはGET
  • こちらが何か情報を送ってそれによって返される情報が変わるときはPOST

ここはAPIの使い方で書かれているはずなので、迷うことはありません。

問題はこの引数が Method型である というところです。

AlamofireにおいてMethod型は以下のようにenum(列挙型)で定義がされています。

QiitaViewer_xcodeproj.png

そのため、この引数は.GETのようにこのうちのいずれかのケースを書く必要があります。

今回アクセスするURLは https://qiita.com/api/v2/items で、
メソッドは GET です。
この情報から引数を埋めると以下のようになります。

ArticleListViewController
func getArticles() {
    Alamofire.request(.GET, "https://qiita.com/api/v2/items") // APIへリクエストを送信
}

 
さて、ここまででリクエストを送ることが出来たので、次は返ってきた情報を取得しましょう。

そのためには、先ほどのrequestメソッドに続けて、responseJSONメソッドを使用します。

responseJSONメソッドは1つの関数を引数に取るので、その関数をクロージャで記述しましょう。

ArticleListViewController_swift_—_Edited.png

上の画像の通り、その関数では Response<AnyObject, NSError>型 の引数を使用できます。

ひとまずこの引数をresponseという名前で扱うこととしましょう。

ArticleListViewController
Alamofire.request(.GET, "https://qiita.com/api/v2/items")
    .responseJSON { response in
        // ここに処理を記述していく
    }

 
さて、responseと定義したResponse型の引数は以下のような4つのプロパティを持ちます。

Alamofire_Response_swift_at_master_·_Alamofire_Alamofire.png

ですが記事の取得に使うのは、4番目のresultだけです。

 
このresultですが、型は Result<Value, Error> となっていますね。

Result型の定義元を見てみるとenumで実装されているようです。

Alamofire_Result_swift_at_master_·_Alamofire_Alamofire.png

列挙型ResultはSuccessもしくはFailureを値に取り、
Successの場合にはValueを持っていることが分かります。

そしてこのValueこそが私たちの求める記事の情報なのです!

AlamofireはこのValueを取得するためにvalueというプロパティを用意してくれています。

ではひとまず記事の情報を取得してprintで表示してみるとしましょう。

コードは以下のようになります。

ArticleListViewController
Alamofire.request(.GET, "https://qiita.com/api/v2/items")
    .responseJSON { response in
        print(response.result.value) // responseのresultプロパティのvalueプロパティをコンソールに出力
    }

ここでviewDidLoadメソッドの最後にgetArticleメソッドの呼び出しを書いてから、実際にRunをしてみましょう。

ArticleListViewController
override func viewDidLoad() {
    // 省略
    getArticles()
}

実行後、コンソールを見てみると何やらデータが表示されていることが分かります。

ArticleListViewController_swift_—_Edited.png

ここまでで記事一覧の情報を取得することが出来ました!

ですが、TableViewにこんなにたくさんの情報を表示は出来ないので、ここから表示したい情報だけを取得しましょう。

記事のデータから欲しい情報のみを取得

さて、TableViewに表示するために、必要なデータのみを取り出したいのですが、先ほど表示された記事一覧はAnyObject型となっていて扱うのが面倒です。

そこでSwiftyJSONの出番ですね。
とりあえずSwiftyJSONをインポートしておきましょう。

ArticleListViewController
import UIKit
import Alamofire
import SwiftyJSON // SwiftyJSONをimport

 
SwiftyJSONはJSON型という、SwiftyJSONが定義した型にデータを変換してからそのデータを扱っていきます。

SwiftyJSON_SwiftyJSON_swift_at_master_·_SwiftyJSON_SwiftyJSON.png

JSON型はAnyObject型からイニシャライズすることが出来るので、
先ほどのresponse.result.valueをアンラップして引数に渡してあげましょう。

ArticleListViewController
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つの関数をとり、複数の要素それぞれに、引数で渡した処理をしてくれます。

QiitaViewer_xcodeproj.png

そして JSON型はSequenceTypeプロトコルに準拠している ので、このメソッドが使えます。

QiitaViewer_xcodeproj.png

実際に使用してみましょう。
クロージャを記述しようとすると、引数にはString型とJSON型とのタプルが使えることが分かります。

ArticleListViewController_swift_—_Edited.png

ここで、Stringにはその要素が何番目かという情報が入り、
JSONには記事1つのデータが入ります。

今回は記事が何番目か、という情報は使わないので 変数名を付けずアンダースコアにしておきます。

ArticleListViewController
let json = JSON(object)
json.forEach { (_, json) in
    // ここに処理を書いていく
}

 
それではここで記事のタイトルを取得してみましょう!

QiitaAPIのドキュメントによると、タイトルは"title"というキーで取得出来るようです。

JSON型は辞書型と同じように[ ]で特定のキーの値を取得することが出来ます。

ArticleListViewController
let json = JSON(object)
json.forEach { (_, json) in
    json["title"] // jsonから"title"がキーのものを取得
}

これはJSON型に subscript が実装されているからですね。
subscriptは配列や辞書型で扱うように、[ ]をメソッドとして使えるようにするものです。
QiitaViewer_xcodeproj.png

また、上の定義から分かるように、subscriptの返り値はJSON型となっています。

TableViewに表示するために、文字列はStringとして扱いたいので型の変換をしましょう。

そのためにはJSON構造体に定義されているstringプロパティを使います。

SwiftyJSON_SwiftyJSON_swift_at_master_·_SwiftyJSON_SwiftyJSON.png

stringプロパティはString型にキャスト出来ればそれを、出来なければnilを返すメソッドで、返り値はString?型となります。

これを使ってさらにprintもしてみましょう。

ArticleListViewController
let json = JSON(object)
json.forEach { (_, json) in
    print(json["title"].string) // 記事タイトルを表示
}

以下のように記事のタイトルだけを取得することが出来ました!

ArticleListViewController_swift.png

 
それでは続いて投稿者のユーザーIDも取得しましょう。
再度QiitaAPIのドキュメントを見ると、投稿者のユーザーIDは、"user"というキーの値の中の、"id"というキーの値に格納されているようです。

つまり二重の辞書型に入ってる訳ですね!

これもSwiftyJSONで簡単に書けます。

ArticleListViewController
let json = JSON(object)
json.forEach { (_, json) in
    json["title"].string
    print(json["user"]["id"].string) // 投稿者のユーザーIDを表示
}

何やらユーザーIDらしきものたちを表示することが出来ました。

ArticleListViewController_swift.png

さて、これで欲しい情報は全て取得できました!

記事の一覧をプロパティに保存

これらの情報をTableViewに表示するために、一度プロパティに保存しておきましょう。

まずはArticleListViewControllerにarticlesというプロパティを作ります。
型はキーがString、値がString?の配列としましょう。

ArticleListViewController
class ArticleListViewController: UIViewController {
    var articles: [[String: String?]] = [] // 記事を入れるプロパティを定義

 
次にforEachメソッドの中でそれぞれの記事の情報をここに入れていきます。
また、保存の処理が終わったら全ての記事が保存出来たかを確認してみます。

ArticleListViewController
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) // 全ての記事が保存出来たら配列を確認

以下のように表示がされていれば正常に保存が出来ています!

ArticleListViewController_swift_—_Edited.png

記事の一覧を表示

最後に記事の表示をします。

まずArticleListTableViewに UITableViewDataSourceプロトコルを採用 しましょう。

ArticleListViewController
class ArticleListViewController: UIViewController, UITableViewDataSource {

そしてtableView:numberOfRowsInSection:メソッドとtableView:cellForRowAtIndexPath:メソッドを定義していきます。

tableView:numberOfRowsInSection:から。
表示したいセルの数は記事数と等しいのでarticlescountプロパティを返しましょう。

ArticleListViewController
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return articles.count
}

そしてtableView:cellForRowAtIndexPath:ですね。
セルを作るのも面倒なので既存のスタイルのセルを使います。

その中でも今回はセルにタイトルとユーザーIDを表示したいので UITableViewCellStyle.Subtitle を使用します。

ArticleListViewController
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = UITableViewCell(style: .Subtitle, reuseIdentifier: "cell") // Subtitleのあるセルを生成

    return cell // cellを返す
}

続いてセルのtextLabeldetailTextLabelにそれぞれ、titleuserIdを表示するよう設定していきましょう。

ArticleListViewController
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に準拠できた ので、
tabledataSourceプロパティにArticleListViewControllerを入れましょう。

ArticleListViewController
table.frame = view.frame
view.addSubview(table)
table.dataSource = self // dataSouceプロパティに自身を代入

 
そして最後に、全ての記事が保存された段階でreloadDataメソッドを実行すれば記事の一覧を表示してくれます!

ArticleListViewController
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を更新

このように表示されていれば完成です。

iPhone_5s_-_iPhone_5s___iOS_9_1__13B137_.png

完成ソースコード

https://github.com/yuta-t/QiitaViewer

ArticleListViewController
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を使ってビルド時間を短縮しよう