Swift + Alamofire + ObjectMapper + Realm [Swift3対応]

そろそろSwiftを社内の案件に導入しようか検討中です。
受託案件ではほぼ必須機能と言える

1. ネットワーク経由でJSON取得
2. JSONをパースしてDBにinsert
3. DBの値をTableViewに表示

という流れ。
せっかくナウな言語であるSwiftなんだから、イケてるライブラリでキメキメなコーディングをしたい！
ということで、Swiftや新しいライブラリの調査がてら、ざっくりとしたサンプルを作ってみました。

## サンプル
サンプルをこちらにあげてみました
[Github - SatoshiN21/RealmSample](https://github.com/SatoshiN21/RealmSample)
※ざっくりとSwift3に対応しております。

今回ははてブのITカテゴリーのホットエントリーRSSをJSON形式で取得しています。
ナウな[Material DesignのCardView](http://www.google.com/design/spec/components/cards.html)っぽい見た目にしてみました。

![iOS Simulator Screen Shot 2015.05.14 19.13.05.png](https://qiita-image-store.s3.amazonaws.com/0/33817/ea41e84f-4026-4669-7a4b-191b97dacdc9.png "iOS Simulator Screen Shot 2015.05.14 19.13.05.png")


## 1. JSON取得
Alamofireではてブのフィード情報を取得しています。
ホットエントリーはRSS形式で配布されていたので、[googleのAPI](https://developers.google.com/feed/v1/jsondevguide#loadContext)を用いてJSON形式に変換しました。

```swift
import Alamofire
import SwiftyJSON

//  google JSON api + hatena bookmark hotentry
let hotEntryUrl = "https://ajax.googleapis.com/ajax/services/feed/load?v=1.0&q=http://b.hatena.ne.jp/hotentry/it.rss&num=100"

// Alamofireを用いてGETリクエスト
Alamofire.request(hotEntryUrl).responseJSON { (response) in
            
    guard response.result.isSuccess, let value = response.result.value else {
                
        // FIXME:便宜上こちら無視してますが、実装時はエラーのハンドリングを行う必要があります
        return
    }
            
    // SwiftyJSON方式
    let json = JSON(value)
｝
```
取得したJSONをSwiftyJSON形式に変換して持ち直します。
今回のサンプルではリクエストエラー時の処理は実装していないので、気をつけてください！

## 2. JSONをパースしてDBにinsert
SwiftyJSON形式にすると、パース作業が捗りますね！チェーン状に目的のエレメントを取得することができます。
エントリー一覧を取得します。

```swift
let json = JSON(responseObject!)
let entries = json["responseData"]["feed"]["entries"]
```


ObjectMapperを用いてEntryオブジェクトにマッピングしています。
ObjectMapperとRealmを共存させる方法は[gologo13さんの投稿](http://qiita.com/gologo13/items/713e3ffccaf77cc79dd4)を参考にさせて頂きました。

```swift
import ObjectMapper

realm.beginWrite()

// JSONをEntryオブジェクトにマッピング
for (_, subJson) in entries {
    if let entry = Mapper<Entry>().map(JSONObject: subJson.dictionaryObject) {
        realm.add(entry, update: true)
    }
}

realm.commitWrite()
```

RealmSwift.Objectを継承したEntryオブジェクト内の仕様はほぼ参考にさせて頂いた記事と同様ですが、
公開日の日付を独自定義したDateTransformでDateに変換しています。


```swift
extension Entry : Mappable {
    
    func mapping(map: Map) {
        content         <- map["content"]
        link            <- map["link"]
        publishedDate   <- (map["publishedDate"] , EntryDateTransform())
        title           <- map["title"]
        contentSnippet  <- map["contentSnippet"]
    }
}

// 独自定義したDateTransform
// フォーマットとNSLocaleを指定してStringをNSDateに変換
class EntryDateTransform : DateTransform {
    override func transformFromJSON(_ value: Any?) -> Date? {
        if let dateStr = value as? String {
            return Date.dateWithString(
                dateStr,
                format: "E, dd MMM yyyy HH:mm:ss zzzz" ,
                locale : Locale(identifier: "en_US"))
        }
        return nil
    }
}

extension Date {
    public static func dateWithString(_ dateStr : String? , format : String, locale : Locale) ->Date? {
        
        guard let dateStr = dateStr else {
            return nil
        }
        let df : DateFormatter = DateFormatter()
        df.locale = Locale(identifier: "en_US")
        df.timeZone = TimeZone.current
        df.dateFormat = format
        return df.date(from: dateStr)
    }
}
```

## 3. DBの値をTableViewに表示
Realmの更新が発生したタイミングでUITableView#reloadData()を実行し、UITableViewの表示を更新。
RealmSwift.Objectを継承したクラスでobjects(type: Object.Type)を呼び出すと、DBに保存されている全ての値が取得できます。

```swift
    func updateTableView() {

        do {
            self.entries = try Realm().objects(Entry.self).sorted(by: { (entry1, entry2) -> Bool in
            let res = entry1.publishedDate.compare(entry2.publishedDate)
            return (res == .orderedAscending || res == .orderedSame)
            })
        }catch {}
        
        tableView.reloadData()
    }
    
    // MARK:- UITableView DataSource / Delegate
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        if let entries = entries {
            return entries.count
        }
        return 0
    }
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        
        let cell  = tableView.dequeueReusableCell(withIdentifier: "CellIdentifier") as! EntryTableViewCell
        
        // if entries have been nil,"cellForRowAtIndexPath:indexPath:" isn't called.
        let entry = entries![indexPath.row]
        
        // date format.
        let df = DateFormatter()
        df.locale = Locale(identifier: "ja_JP")
        df.timeZone = TimeZone.current
        df.dateFormat = "MM/dd"
        let dateStr = df.string(from: entry.publishedDate as Date)
        
        cell.titleLabel.text = [dateStr,entry.title].joined(separator: " ")
        cell.descriptionLabel.text = entry.contentSnippet
        
        return cell
    }
```

## 以上
かなりざっくりとではありますが、JSONを取得し、内部DBに保存/表示の方向性は見えたかなという感じです。
Objective-Cの時はCoreDataを使っていましたが、データが肥大化するほどみるみるパフォーマンスが落ちていく様に呆然としていた記憶があります。
Realmを使ってどれほどパフォーマンスに差が出るのか、いまから楽しみです。

## 参考にさせて頂いた記事
[[Swift]ObjectMapperを使ったRealmモデルへのJSONマッピング - Qiita](http://qiita.com/gologo13/items/713e3ffccaf77cc79dd4)
[情弱がSwift用のObjectMapper読んでみた - Qiita](http://qiita.com/stenpel/items/66350f6a23daf2cf894c)