JavaScriptプログラマがSwift iOSアプリを2週間で作って公開してみた〜その6 Twitter Search API with Fabric〜


JavaScriptプログラマー(JSer)がSwiftデビューして、ただ作りたいアプリを作ってみたシリーズ第6回目です。

前回はUITabBarController & UINavigationControllerを使って認証後に遷移するページの土台を作る方法について紹介しました。

今回は土台として作ったページでTweet情報を表示するために使用するTwitter Search APIについてです。

今回作成したページでは「記事」と「お気に入り」のタブでそれぞれ以下のようなTweetを表示します。

  • 記事: デフォルトではURLを含み、画像を含まない自分のTweetを表示。HashTagを設定するとそのhashtagを含み、画像を含まない自分のTweetを表示
  • お気に入り: 自分がFavoriteしたTweetを表示

実装方針

自分がシェアしたTweetの表示

特定のユーザーのTweetを表示するために使うAPIで代表的なものは2つあります。

  • GET statuses/user_timeline: 特定のユーザーのつぶやきを取得。TwitterのUIで言うとhttps://twitter.com/{user_id}のページで表示されている内容に対応。いくつかのオプションは指定可能(例えば、exclude_repliesでリプライを含むか含まない等)
  • GET search/tweets: queryを指定してTweetの検索を行うAPI。公式ドキュメントにあるようにqueryを工夫することで様々な条件でTweetの取得が可能

一見、今回のアプリで使えそうなのはuser_timelineの方かと当初は思っていましたが、今回使用しているのはSearch APIです。理由は、今回作ったアプリは自分のTweet情報を取得の中でURLを含むものやハッシュタグを含むものをフィルターする必要があったからです。

user_timelineの方はURLを含むもの、とか、imageを含むものというようなqueryの指定は現在はできません。responseをcheckしてフィルターするのは手かもしれませんが、ページングなどを考えた時に、表示する件数とAPIで取得する件数が大きくずれる可能性(例えば、自分のTweetでURLを含むものが3ページング目に初めて現れるケースなど)があるため、Search APIでqueryを工夫するようにしました。

Twitter Search APIで指定するクエリの例

特定のユーザのTweet from:{user_id}
URLを含む filter:links
画像を含まない -filter:images

今回のアプリでは例えば以下のようなクエリを作成してAPIのqパラメーターに渡します。(自分のTweet + リンクを含むもの + 画像を含まないもの)
from:tejitak+filter:links+-filter:images

また、設定画面で「#あとで読む」ハッシュタグを指定した場合は以下のようになります。
from:tejitak+#あとで読む

したがって、今回のアプリでは自分のURLを含むTweet等を表示する際に検索APIを使って実装します。

ちなみに実はこのqueryそのままTwitterのUIの検索ボックスで使えます。自分のTweet + リンクを含むもの + 画像を含まないものとして”from:tejitak+filter:links+-filter:images”を指定するとちゃんと取れてるのが確認できます。

スクリーンショット 2015-02-05 23.11.28

自分がお気に入りした記事の取得

こちらはそのまま特定のユーザーのお気に入りTweetをリストするAPIGET favorites/listを使えばOKです。

Tweetの検索結果の取得

上記の実装方針に沿って、Swift + Fabricでtwitter Search APIをcallしていきます。
TwitterAPI.swiftというファイルを作成して、parameter, success callback, fail callbackを受け取る以下のようなclass functionを用意しました。

TwitterAPI.swift

    class func search(params: [NSObject : AnyObject]!, tweets: [TWTRTweet]->(), error: (NSError) -> ()) {
        self.callAPI("/search/tweets.json", parameters: params, {
            response, data, err in
            if err == nil {
                var jsonError: NSError?
                let json: AnyObject? =  NSJSONSerialization.JSONObjectWithData(data,
                    options: nil,
                    error: &jsonError)
                if let top = json as? NSDictionary {
                    var list: [TWTRTweet] = []
                    if let statuses = top["statuses"] as? NSArray {
                        list = URLTweet.tweetsWithJSONArray(statuses) as [TWTRTweet]
                    }
                    tweets(list)
                }
            } else {
                error(err)
            }
        })
    }

    class func callAPI(path: String, parameters: [NSObject : AnyObject]!, completion: TWTRNetworkCompletion!){
        self._callAPI(path, method: "GET", parameters: parameters, completion)
    }

    class func _callAPI(path: String, method: String, parameters: [NSObject : AnyObject]!, completion: TWTRNetworkCompletion!){
        let api = TwitterAPI()
        var clientError: NSError?
        let endpoint = api.baseURL + api.version + path
        let request = Twitter.sharedInstance().APIClient.URLRequestWithMethod(method, URL: endpoint, parameters: parameters, error: &clientError)
        if request != nil {
            Twitter.sharedInstance().APIClient.sendTwitterRequest(request, completion: completion)
        }
    }

searchのfunctionに関してはJSに慣れている人は、よくあるようなfunctionかと思います。parameterは以下のような形式でswiftで簡単に作ることができるマップを渡します。

パラメーターの例: [“q”: “from:tejitak+#あとで読む”, “result_type”: “recent”, “count”: “40”]

Fabricを使っているとAPIの呼び出しは_callAPIのfunctionの中にあるようにTwitter.sharedInstance().APIClientを使ってAPIリクエストを発行します。ここがjQueryでいう$.ajaxにあたるところかと。

Search APIのresponseはjsonで返ってくるのでTweetのリストを取得するために、JSONをたどる必要があります。
ここがJSと比べるとしんどいところです。jsonのライブラリを使うとラクという記事もありましたが、今回はデフォルトのフレームワークに同梱のJSONパーサーNSJSONSerializationを使用しました。

searchのfunctionの中で辿っている方法を解説すると、以下のコメントのようにas?でキャストしつつif文でチェックして辿っていきます。

// response jsonの想定形式: {"statuses": [{},,,]}
var jsonError: NSError?
// json(response data)をswiftで扱えるオブジェクトに変換する
let json: AnyObject? =  NSJSONSerialization.JSONObjectWithData(data, options: nil, error: &jsonError)
// まずresponse jsonがマップ形式かNSDictionaryにcastしてチェック
if let top = json as? NSDictionary {
     var list: [TWTRTweet] = []
     // statusesプロパティがarrayかどうかをNSArrayにcastしてチェック
     if let statuses = top["statuses"] as? NSArray {
         // 各tweetブジェクトをfabricが提供するクラスに変換するユーティリティを使う
         list = URLTweet.tweetsWithJSONArray(statuses) as [TWTRTweet]
     }
     // 受け取っていたsuccess callbackを呼ぶ
     tweets(list)
}

このfunctionを呼ぶviewController側でsuccess callback(クロージャ)を渡します。今回の記事では解説しませんが、基本的には結果のTweetクラスとTableCellを使ってUIに表示する処理です。

お気に入りリストの取得

こちらも検索APIを叩く処理とほとんど同じです。ただし、検索APIのresponseとjsonの形式が違うので、そこは変わります。

TwitterAPI.swift

    class func listMyFavorites(params: [NSObject : AnyObject]!, tweets: [TWTRTweet]->(), error: (NSError) -> ()) {
        self.callAPI("/favorites/list.json", parameters: params, {
            response, data, err in
            if err == nil {
                var jsonError: NSError?
                let json: AnyObject? =  NSJSONSerialization.JSONObjectWithData(data,
                    options: nil,
                    error: &jsonError)
                if let jsonArray = json as? NSArray {
                    tweets(URLTweet.tweetsWithJSONArray(jsonArray) as [TWTRTweet])
                }
            } else {
                error(err)
            }
        })
    }

Favoriteの方はresponseがそのままjson arrayで返ってくるため、jsonを辿る処理は少し簡潔です。

パラメーターに関しては、今回のアプリではcountとスクロールしてページングするためのmax_idを使用しています。

パラメーターの例: [“count”: “40”, “max_id”: “123456789”]

記事のタブの処理と同様に、お気に入りのタブのviewControllerがcallbackを渡します。
ページングやTableCellの実装についての解説はまた別の記事でするので乞うご期待。

以上、今回はFabricを使ってTwitter APIを叩く部分の解説でした。次回は続きで、UITableViewCellを使ってtweetを表示していきます。

TwitStockerのダウンロードはこちらから。
https://itunes.apple.com/en/app/twitstocker/id958798898?l=ja&ls=1&mt=8