LoginSignup
32

More than 3 years have passed since last update.

【Swift4】Realm+Codableを使ったお手軽なDB Part.1(モデル編)

Last updated at Posted at 2018-10-30

はじめに

みなさん、こんにちは。株式会社NexceedでiOSエンジニアをしている@cottpanです😎
アプリの開発を行う中で、データベースの操作は避けて通れない操作だと思います。

  • APIを叩いてJSONをパース
  • パースしたデータを、アプリのDBに保存
  • JSONエンコーディングして、データ送信

この記事では、これらの操作をお手軽に実現でき、パフォーマンスもそこそこ良い、Realm、Codableを使った方法を説明したいと思います。

Realm

Realmとは、Core DataやSQLiteといった従来のDBより、高速で使いやすいといった特徴がある、モバイル向けに開発されたDBです。
Realm: Create reactive mobile apps in a fraction of the time
インストール方法はこの記事では割愛しますが、CocoaPodsでインストール出来ます。

Codable

Swift4から新たにFoundationに追加され、簡単にJSONのデコード、エンコードを行うことができる仕組みが提供されました。これを使うことで、他のフレームワークなしに、複雑なJSONも一発で変換できます!

まずやってみる

今回は以下のようなJSONデータを利用するとします。

{
    "name": "hogehoge",
    "id": 553,
}

モデルの作成

このデータを格納するための、モデルクラスを作成します。Codableに準拠させるので、JSONパースができ、そのままRealmのテーブルを作るためのクラスとしても使用できます。

UserData.swift
import Foundation
import RealmSwift

class UserData: Object, Codable {
    // カラム定義
    @objc dynamic var name: String = ""
    @objc dynamic var id: Int = 0
    // プライマリキーの定義
    override public static func primaryKey() -> String? {
        return "id"
    }
}

エンコード、デコードメソッドの実装は、自動で行ってくれるので完成です。
これで準備は整いました。あとはJSON→Realmの作業をやってみましょう。

実行

// Realmのインスタンスを準備
let realm = try! Realm()
let dataStr = """
{
    "name": "hogehoge",
    "id": 553,
}
"""
let data = dataStr.data(using: .utf8)!

// JSONをUserDataクラスのオブジェクトにパース
let obj = try! JSONDecoder().decode(UserData.self, from: data)

// Realmに書き込み
try! realm.write {
    realm.add(obj)
}

これでRealmにデータを格納することが出来ました。とっても簡単ですね😀
アプリのDocuments直下にデータベースの実体があるので確認してみましょう。拡張子が.realmになっているものがデータベースです。このファイルはMac App Storeで配布されているRealm Browserを使用することで確認できます。
スクリーンショット 2018-10-27 18.04.08.png
ちゃんと追加できることが確認できました!
今度はRealmからデータを取得し、JSON形式として出力してみます!

let obj = realm.object(ofType: UserData.self, forPrimaryKey: 553)
let encoder = JSONEncoder()
let data = try! encoder.encode(obj)
let jsonStr = String(data: data, encoding: .utf8)!
print(jsonStr)

XcodeのコンソールにJSON形式の文字列が出力されました😃
スクリーンショット 2018-10-27 18.12.39.png

ポイント

変数

変数は@objc dynamic属性を定義する必要があります。またInt,Bool,Float,Double型に関してはオプショナルにする場合、RealmOptional<Type>として宣言する必要があります。宣言できる型は以下の表を参考にしてください。

Type Non-optional Optional
Bool @objc dynamic var value = false let value = RealmOptional<Bool>()
Int @objc dynamic var value = 0 let value = RealmOptional<Int>()
Float @objc dynamic var value: Float = 0.0 let value = RealmOptional<Float>()
Double @objc dynamic var value: Double = 0.0 let value = RealmOptional<Double>()
String @objc dynamic var value = "" @objc dynamic var value: String? = nil
Data @objc dynamic var value = Data() @objc dynamic var value: Data? = nil
Date @objc dynamic var value = Date() @objc dynamic var value: Date? = nil
Object n/a: must be optional @objc dynamic var value: Class?
List let value = List<Type>() n/a: must be non-optional
LinkingObjects let value = LinkingObjects(fromType: Class.self, property: "property") n/a: must be non-optional

Realm -> Property cheatsheetより引用)

自動Codable準拠

String, Int, Double, Date, Data, URLなどは既にCodableに準拠しているため、明示的にencode,decodeメソッドを実装する必要はありません。しかし、Date型やオプショナル型などそのままでは使えない場合があるので、自分でencode,decodeメソッドを実装する必要があります。

応用編

様々な型のデータ型に対応できるように、encode,decodeメソッドを自前で実装する場合をまとめます。

{
    "id": 553,
    "name": "hogehoge",
    "birthday": "1999-01-02T10:45:22+09:00",
    "favorite_food": "apple",
    "is_from_japan": true,
    "favorite_song": null,
    "age": null
}

先程のJSONを拡張して、データを追加してみましょう。Date型、NOT NULLの変数も含まれています。
このデータを読み込めるようにUserDataクラスを編集します。

UserData.swift
class UserData: Object, Codable {
    // カラム定義
    @objc dynamic var id: Int = 0
    @objc dynamic var name: String = ""
    @objc dynamic var birthday: Date = Date()
    @objc dynamic var isFromJapan: Bool = false
    @objc dynamic var favoriteSong: String?
    let age = RealmOptional<Int>()

    // プライマリーキーの定義
    override public static func primaryKey() -> String? {
        return "id"
    }

    private enum CodingKeys: String, CodingKey {
        case id
        case name
        case birthday
        case isFromJapan = "is_from_japan"
        case favoriteSong = "favorite_song"
        case age
    }
    required convenience public init(from decoder: Decoder) throws {
        self.init()
        let container = try decoder.container(keyedBy: CodingKeys.self)

        id = try container.decode(Int.self, forKey: .id)
        name = try container.decode(String.self, forKey: .name)
        isFromJapan = try container.decode(Bool.self, forKey: .isFromJapan)
        favoriteSong = try container.decodeIfPresent(String.self, forKey: .favoriteSong)
        age.value = try container.decodeIfPresent(Int.self, forKey: .age)

        let dateFormatter = DateFormatter()
        dateFormatter.locale = Locale(identifier: "en_US_POSIX")
        dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ"
        let birthdayStr = try container.decode(String.self, forKey: .birthday)
        birthday = dateFormatter.date(from: birthdayStr)!
    }
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)

        try container.encode(id, forKey: .id)
        try container.encode(name, forKey: .name)
        try container.encode(isFromJapan, forKey: .isFromJapan)
        try container.encode(favoriteSong, forKey: .favoriteSong)
        try container.encode(age.value, forKey: .age)

        let dateFormatter = DateFormatter()
        dateFormatter.locale = Locale(identifier: "en_US_POSIX")
        dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ"
        dateFormatter.timeZone = TimeZone.current
        let bitrhdayStr = dateFormatter.string(from: birthday)
        try container.encode(bitrhdayStr, forKey: .birthday)

    }
}

先ほどと比べてかなり長くなってしまいましたが、追加した箇所を説明してみます。

CodingKey

JSONのキーと、Swiftクラスの変数名が異なる場合にはCodingKeyを定義することで、読み込むことが出来ます。

init(decode)メソッド

変数ごとに、型を指定してデコードします。

  • non-optionalならば、decodeメソッド
  • optionalならば、decodeIfPresentメソッド
  • RealmOptional<Type>ならば、[variable].valueに対して、decodeIfPresentメソッド

また、convenienceイニシャライザを使用するので、オプショナルでない変数に対しては、初期値を適当に設定してください。日付に関しては、ISO8601拡張形式でのDateを読み込むため、DateFormatterを使用して、明示的に形式を指定しています。

encodeメソッド

decodeメソッドの逆を行っているだけなので、行数は多いですが、わかりやすかと思います。
Date型に関しては、Dateformatterを用いて、Stringに一度変換してからencodeを行っています。

さいごに

いかかでしたでしょうか?RealmとCodableを使うことで、データのやり取りも含めたデータベースが取り組みやすくなるかと思います。

続編書きました!
【Swift4】Realm+Codableを使ったお手軽なDB Part.2(リレーション編)
【Swift4】Realm+Codableを使ったお手軽なDB Part.3(クエリ編)
【Swift4】Realm+Codableを使ったお手軽なDB Part.4(番外編)


株式会社Nexceed にて、一緒に働いてくれる仲間を募集中です:point_down::point_down::point_down:
- 建築業界に革命を!!AI × BIMの世界を作り出すアプリエンジニア募集!

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
32