炊きたてのご飯が食べたい

定時に帰れるっていいね。自宅勤務できるっていいね。子どもと炊きたてのご飯が食べられる。アクトインディでは積極的にエンジニアを募集中です。

( Swift2 ) Alamofire + ObjectMapper + Realm でリクエストを Realm に自動的に保存する


Swift2.2, Xcode, Realm1.0.0

Alamofire と ObjectMapper を利用して API 通信を行っている場合、リクエストが成功した際のレスポンスをそのまま Realm に突っ込んで保存したいケースがあったりします。前回作成した API クライアント(Swift2 Alamofire + ObjectMapper で API クライアントを作成する)を拡張して、リクエストが成功したら結果を自動的に Realm に保存する方法を紹介したいと思います。

  1. Realm への保存を行うメソッドを定義した RealmStorable プロトコルを作成する
  2. ObjectMapper の Mappable を採用した Entity に RealmStorable も採用する
  3. API のリクエスト成功時の formJson メソッドを拡張して、レスポンスを Realm に保存する

1. Realm への保存を行うメソッドを定義した RealmStorable プロトコルを作成する

RealmStorable を採用した Entity は API のリクエストを自動的に Realm に保存するといった作戦です。

  • RealmStorable.swift
import RealmSwift

/**
 API のレスポンスを自動的に Realm に保存したいエンティティに設定するプロトコル
*/

protocol RealmStorable {
    static func store(object: Object)
}

extension RealmStorable {
    // store メソッドは APIのリクエスト成功時の fromJson メソッド内で呼び出されます
    static func store(object: Object) {
        let realm = try! Realm()
        try! realm.write {
            realm.add(object, update: true)
        }
    }
}

2. ObjectMapper の Mappable を採用した Entity に RealmStoreble も採用する

今回は、 id で本を検索したら、タイトルと著者をレスポンスで返す API を例にしたいと思います。

  • API.call(Endpoint.Book.Find(1)) といった形でリクエストを行う
  • レスポンスは { id: 1, title: "炊きたてのご飯", author: "山田太郎" } といった形式で返却される

上記の場合、本のデータとなる Entity は ObjectMapper を利用して以下のように定義できます。

  • BookEntity.swift
import ObjectMapper

class BookEntity: Mappable {
    var id: Int?
    var title = ""
    var author = ""

    required init?(_ map: Map) {}

    func mapping(map: Map) {
        self.id <- map["id"]
        self.title <- map["title"]
        self.author <- map["author"]
    }
}

BookEntity を Realm に保存したいので BookEntity に Realm の Object を採用します。ObjectMapper の公式サイトに記載がありますが ObjectMapper と Realm を共存させたい時は以下のように書きます。

import RealmSwift
import ObjectMapper

class BookEntity: Object, Mappable {
    dynamic var id: Int?
    dynamic var title = ""
    dynamic var author = ""

    override static func primaryKey() -> String {
        return "id"
    }

    required convenience init?(_ map: Map) {
        self.init()
    }

    func mapping(map: Map) {
        self.id <- map["id"]
        self.title <- map["title"]
        self.author <- map["author"]
    }
}

これで ObjectMapper でマッピングされた Entity を Realm に保存できるようになりました。後は Realm に保存したい Entity なので RealmStoreble を採用してあげます。

import RealmSwift
import ObjectMapper

class BookEntity: Object, Mappable, RealmStoreble {
    dynamic var id: Int?
    dynamic var title = ""
    dynamic var author = ""

    override static func primaryKey() -> String {
        return "id"
    }

    required convenience init?(_ map: Map) {
        self.init()
    }

    func mapping(map: Map) {
        self.id <- map["id"]
        self.title <- map["title"]
        self.author <- map["author"]
    }
}

3. API のリクエスト成功時の formJson メソッドを拡張して、レスポンスを Realm に保存する

最後に、前回の記事で作成した APIProtocol の fromJson メソッドを RealmStoreble 用に拡張します。

  • APIProtocol.swift
import Alamofire
import ObjectMapper

protocol RequestProtocol: URLRequestConvertible {
    ... 省略
    
    func fromJson(json: AnyObject) -> Result<ResponseType, NSError>
}

extension RequestProtocol {
    ... 省略

    func fromJson(json: AnyObject) -> Result<ResponseType, NSError> {
        ... 省略
    }
}

extension RequestProtocol where ResponseType: Mappable {
    func fromJson(json: AnyObject) -> Result<ResponseType, NSError> {
        ... 省略
    }
}

extension APIUsable where ResponseType: protocol<RealmStorable, Mappable> {
    func fromJson(json: AnyObject) -> Result<ResponseType, NSError> {
        guard let value = Mapper<ResponseType>().map(json) else {
            let errorInfo = [ NSLocalizedDescriptionKey: "Mapping object failed" , NSLocalizedRecoverySuggestionErrorKey: "Rainy days never stay." ]
            let error = NSError(domain: "com.example.app", code: 0, userInfo: errorInfo)
            return .Failure(error)
        }
        guard let object = value as? Object else {
            let errorInfo = [ NSLocalizedDescriptionKey: "Realm object failed" , NSLocalizedRecoverySuggestionErrorKey: "Rainy days never stay." ]
            let error = NSError(domain: "com.example.app", code: 0, userInfo: errorInfo)
            return .Failure(error)
        }
        // ResponseType は RealmStorable を採用しているので store メソッドを呼び出すことができる
        ResponseType.store(object)
        return .Success(value)
    }
}

これで API.call(Endpoint.Book.Find(1)) とリクエストを投げ、処理が正常終了したら ResponseType.store(object) の実行により Realm に自動的に保存されるようになりました。