NSAtomicStoreサブクラスの作成のメモ

CoreDataがサポートしてい保存形式

CoreDataはデータ保存形式として予め以下の4つの形式をサポートしている。

  指定文字列 description
SQLite NSSQLiteStoreType SQLiteに保存する
バイナリ NSBinaryStoreType バイナリ形式でfileに保存する
XML NSXMLStoreType XML形式でfileに保存する。iOSにはない。
in Mmemory NSInMemoryStoreType メモリだけ使用し保存しない。

これらのStoreTypeを目的に応じて使い分けることができる。そして目的に合わない場合に、自作することも可能である。

クラス階層

ここでは、NSBinaryStoreTypeやNSXMLStoreTypeと同じatomicな読み書きを行い、独自のファイ形式をサポートするStoreタイプの作成方法を説明する。

まずは、既存のStoreTypeがどのクラス階層に属しているかを示す。 残念ながら、既存のStoreTypeのクラスはドキュメント化されていないので指定用文字列をクラス名の代わりに使用している。

../../../_images/class.png

クラスNSPersistentStoreは、CoreDataで使用する永続化方式の抽象クラスです。このクラスのサブクラスを書くことで、CoreDataのデータを任意の媒体に保存できるようになる。 デザインパターンで言うところの、テンプレートパタンやストラテジーパタンで設計されている。

NSIncrementalStoreは、部分的に保存することができるStoreの抽象クラスです。既存のクラスではNSSQLiteStoreTypeがこのクラスを継承している。他のデータベースやNoSQL的なものをサポートしたい場合は、このクラスを継承して作ることになる。

NSAtomicStoreは、データを保存するときに一括で保存する必要があるStoreの抽象クラスです。既存のクラスではNSBinaryStoreTypeとNSXMLStoreTypeがこのクラスを継承している。

このNSAtomicStoreのサブクラスを作成することで、CoreDataの保存形式に独自のファイルフォーマットを導入する。

実行時のオブジェクトグラフとNSAtomicStoreCacheNode

NSAtomicStoreのサブクラスの内部では、NSManagedObjectのデータをNSAtomicStoreCacheNodeに対1で転写して使用する。 NSAtomicStoreCacheNodeはサブクラス化しなくとも問題なく使える。

NSAtomicStoreCacheNodeとNSManagedObjectとNSAtomicStoreの関係は、以下の3点だけ覚えておけば問題ないはず。

  1. NSAtomicStoreCacheNodeはNSAtomicStoreに所有される。
  2. NSAtomicStoreCacheNodeはNSManagedObjectと1:1で関係する。
  3. NSAtomicStoreCacheNodeを区別する識別子を割り当てる。

図で示すと、以下のようになる。

../../../_images/objectGraph1.png

NSAtomicStoreをサブクラス化するには

NSAtomicStoreをサブクラス化するにあたっての、上書きすべきメソッドは大別して2つのグループに分かれる。

メタデータの管理系のメソッド

- (id)initWithPersistentStoreCoordinator:(NSPersistentStoreCoordinator*)coordinator
                       configurationName:(NSString*)configurationName
                                     URL:(NSURL*)anUrl
                                 options:(NSDictionary *)options

+ (NSDictionary *)metadataForPersistentStoreWithURL:(NSURL*) url
                                              error:(NSError*__autoreleasing*) error

+ (BOOL)        setMetadata:(NSDictionary*) metadata
  forPersistentStoreWithURL:(NSURL*) url
                      error:(NSError*__autoreleasing*) error

- (NSString*)type
- (NSString*)identifier
  • initWithPersistentStoreCoordinator…は、初期化メソッド。ここで、メタデータを読み込むか初期化する必要がある。
  • metadataForPersistentStoreWithURL…は、メタデータの読み込みメソッド。指定されたURLからメタデータを読み込む。
  • setMetadata…は、メタデータの書き込みメソッド。指定されたURLからメタデータを書き込む。
  • typeは、クラスの識別子を返します。この識別子によって、CoreDataは新たに作成したNSAtomicStoreのサブクラスを認識できます。[NSPersistentStoreCoordinator registerStoreClass:forStoreType:]に渡す文字列。NSSQLiteStoreTypeやNSXMLStoreTypeと同じ目的の文字列。
  • identifierはインスタンスの識別子です。保存して再生した場合は、また同じ値を返す必要があります。そーでないと識別子として動作しない。

NSAtomicStoreCacheNodeの管理系のメソッド

- (BOOL)load:(NSError*__autoreleasing*) error
- (BOOL)save:(NSError*__autoreleasing*) error

- (NSAtomicStoreCacheNode*)newCacheNodeForManagedObject:(NSManagedObject*) inManagedObject
- (void)updateCacheNode:(NSAtomicStoreCacheNode*)node fromManagedObject:(NSManagedObject*)managedObject
- (id)newReferenceObjectForManagedObject:(NSManagedObject*) managedObject
  • load…はファイルからデータを読み込ませる。
  • save…はファイルへ保存させる。
  • newCacheNodeForManagedObject..は与えられたNSManagedObjectに対応するNSAtomicStoreCacheNodeを作成する
  • updateCacheNode…はnodeへmanagedObjectの内容を反映させる。
  • newReferenceObjectForManagedObjectはNSManagedObjectの識別子を生成する。保存して再生した場合は、また同じ値を返す必要があります。そーでないと識別子として動作しない。

ファイルフォーマットの設計

NSAtomicStoreのサブクラスを作成して、CoreDataを任意のファイルフォーマットに対応させるにはいくつかの条件がある。

  1. ファイルを区別するためのNSStoreUUIDKeyの値の保存が必要。メソッドidentifierがその値を返す必要がある。
  2. レコードに相当するNSManagedObjectを区別するための、リファレンスの保存が必要。
  3. テーブルに相当する値を何らかの方法で識別する必要がある。

ここで、plist形式やxml形式などの汎用ファイルフォーマット形式であれば対応可能。既存のファイルフォーマットに上記の条件を合わせるには何らかの工夫が必要になる。

全てのCoreDateのファイルを保存するのでなければ、それなりに簡単にできる。

ここでは、以前作成したToDoアプリ元にcsv形式での読み書きをサポートさせてみる。

以下のようにした。

  1. 1行目はメタファイル行。metaDataのUUIDの値
  2. 2行目以降は通常のcsvファイル
  3. ToDoアプリのみサポート。テーブル名は保存しない。
  4. 項目は、done, memo, sortOrder, 作成日, uuid(レコードのuuid値)

具体的にはこんな感じ。

#D35CF030-1EC1-4667-9666-70C8247A1010
1, insertCurrent, 2, 2016-09-17 18:15:00 +0000, E899556C-BE1A-42A3-A634-F8411915C05F
0, insertCurrent, 5, 2016-09-17 18:15:00 +0000, 803054FC-C497-4DD7-A078-CF592B994EB4
1, insertCurrent, 3, 2016-09-17 18:15:00 +0000, 569485BA-A1E2-4E5B-88B5-6D5DB2C8AB8F
0, insertCurrent, 1, 2016-09-17 18:15:00 +0000, 65A95220-A09F-4000-9F3C-EC8448D5DCB7
0, addLast, 0, 2016-09-17 18:15:00 +0000, F7C54A12-8CA6-4A4A-9D45-1A92E23922F9
1, insertCurrent, 4, 2016-09-17 18:15:00 +0000, 5CE1C138-32A3-4CE8-9917-84C941011C09

CoreDataへの登録とinfo.plistへの追加

NSAtomicStoreのサブクラスを使用するには、NSPersistentStoreCoordinatorに登録する必要がある。 適当な場所に以下のコードを書いて、登録する。applicationDidFinishLaunchingが良いと思う。

[NSPersistentStoreCoordinator registerStoreClass:[TDLAtomicStore class]
                                    forStoreType:TDLAtomicStoreType];

同じく、NSPersistentDocumentと一緒に使う場合は、info.plsitにDocument typeを追加する。

<key>CFBundleDocumentTypes</key>
               <dict>
                       <key>CFBundleTypeExtensions</key>
                       <array>
                               <string>csv</string>
                       </array>
                       <key>CFBundleTypeMIMETypes</key>
                       <array>
                               <string>text/csv</string>
                       </array>
                       <key>CFBundleTypeName</key>
                       <string>CSV</string>
                       <key>CFBundleTypeRole</key>
                       <string>Editor</string>
                       <key>LSTypeIsPackage</key>
                       <integer>0</integer>
                       <key>NSDocumentClass</key>
                       <string>TDLDocument</string>
                       <key>NSPersistentStoreTypeKey</key>
                       <string>TDLAtomicStoreType</string>
               </dict>

と登録しておけばOK。これで、.csvの拡張子のファイルを開けるようになるし、”save as..”でTDLAtomicStoreTypeの保存方式が使えるようになる。

ソースコードの解説

めんどくさくなってきたので、Cocoa勉強会Matudoで説明します。https://connpass.com/event/46754/

ファイルTDLCSVAtomicStore.h.mにまとまってます。

ソースコードはここ。( ModernToDoList_atomicStore.zip )