iOS
CoreData
Swift

CoreDataで自作クラスのAttributeを保存する方法

概要

CoreData noobということもあって、AttributeのTypeに標準で用意されていない型をTransformableを使用して保存したい時に戸惑ってしまったので、備忘録として残しておきます。

恐らく、正解の流れとしては

  1. 保存したい自作クラスを定義
  2. そのクラスをNSCodingプロトコルに準拠させる
  3. エンコード処理とデコード処理の実装
  4. EntityのTypeをTransformableに設定する

です。以下で詳しく述べます。
Swiftのバージョンは4.1です。

自作クラスの定義

User.swift
class User: NSManagedObject {
  @NSManaged var name: String
  @NSManaged var pet: Pet
}

class Pet {
  var name: String?
  var gender: Gender?
}

enum Gender: Int {
  case male = 0
  case female
}

上記は、今回扱うデータモデルです。Userは名前と飼っているペットを持ち、Petは名前と性別を持っていることにします。UserクラスをCoreDataのEntityとして扱う為、NSManagedObjectを継承させ、プロパティには@NSManagedを付けています。

この記事のゴールは、petプロパティをAttributeとして保存できるようにすることです。

さて、上記のコードをそのまま書くと、「petプロパティは@NSManagedを使えんぞ!」と怒られてしまいます。どうやら、Obj-Cで表現できるクラスでなければならないようです。ですので、PetクラスにNSObjectを継承させます。

User.swift
...

class Pet: NSObject {
  var name: String?
  var gender: Gender?
}

...

これで怒られなくなりますが、まだCoreDataで扱える形にはなっていません。

NSCodingに準拠させる

続いて、PetクラスをNSCodingに準拠させ、エンコード処理とデコード処理を実装します。

User.swift
...

class Pet: NSObject, NSCoding {
  var name: String?
  var gender: Gender?

  func encode(with aCoder: NSCoder) {
    aCoder.encode(self.name, forKey: "Name")
    aCoder.encode(self.gender!.rawValue, forKey: "Gender")
  }

  required init?(coder aDecoder: NSCoder) {
    super.init()

    self.name = aDecoder.decodeObject(forKey: "Name") as? String
    let genderValue = aDecoder.decodeInteger(forKey: "Gender")
    self.gender = Gender(rawValue: genderValue)
  }
}

...

これでPetクラスをCoreDataで扱えるようになりました!

ここで注意すべき(僕が躓いた)ところは、enumもそのまま保存できるわけではないので、一旦rawValueを介してエンコード/デコードする必要があるところです。当然といえば当然なのですが...

Transformableに設定

最後に、AttributeのTypeをTransformableに変更して完了です。
スクリーンショット 2018-09-13 13.07.34.png

どうでもいいですけど、"AttributeのTypeをTransformableにする"って文章ヤバイですね。ヤバイ...(語彙力0)