ObjecTips

Swift & Objective-C で iOS とか macOS とか

Core Dataのデータベースの保存場所を切り替える

保存場所を固定的に変更する方法は前回の記事を参照

koze.hatenablog.jp

今回の保存場所の切り替えというのは既に使用している A.sqlite ファイルから別の B.sqlite を使用するようにしたりまた A.sqlite に戻したりするという意味での動的な切り替え。
利用ケースの想定としては

  • ローカルDBとクラウド上のデータのローカルキャッシュのDBを分けて管理する *1
  • サービスのアカウント毎にローカルキャッシュのDBファイルを切り替える *2
  • ローカルデータを削除せず、テストデータを読み込ませてまた後にローカルデータへ戻す *3

等々。

実装

実装はシンプル。Xcode のテンプレートに沿って AppDelegate への実装とする。
まず NSPersistentContainer の管理クラス(ここでは AppDelegate)にDBのURLを設定出来るようにする。

#import <UIKit/UIKit.h>
#import <CoreData/CoreData.h>

@interface AppDelegate : UIResponder <UIApplicationDelegate>

@property (strong, nonatomic) UIWindow *window;
@property (readonly, strong) NSPersistentContainer *persistentContainer;
@property (strong) NSURL *databaseURL; // <-- here

- (void)saveContext;

@end

databaseURL が設定されたら値を保持して、persistentContainer を nil にする。

- (void)setDatabaseURL:(NSURL *)databaseURL
{
    _databaseURL = databaseURL;
    _persistentContainer = nil;
}

persistentContainer の初期化コードにこの databaseURL を使って初期化するよう仕込む。
初期化処理は次に persistentContainer メソッドが呼ばれる時に実行される。

- (NSPersistentContainer *)persistentContainer {
    @synchronized (self) {
        if (_persistentContainer == nil) {
            _persistentContainer = [[NSPersistentContainer alloc] initWithName:@"AppName"];
            if (self.databaseURL) {
                NSPersistentStoreDescription *storeDescription = _persistentContainer.persistentStoreDescriptions.firstObject;
                storeDescription.URL = self.databaseURL;
            }
            [_persistentContainer loadPersistentStoresWithCompletionHandler:^(NSPersistentStoreDescription *storeDescription, NSError *error) {
                if (error != nil) {
                    NSLog(@"Unresolved error %@, %@", error, error.userInfo);
                    abort();
                }
            }];
        }
    }
    
    return _persistentContainer;
}

以下の様に databaseURL を変更した後に persistentContainer へのアクセスが発生すると指定の場所へDBが作られる事になる。

    NSURL *databaseURL = [[NSURL fileURLWithPath:NSTemporaryDirectory()] URLByAppendingPathComponent:@"Test.sqlite"];
    appDelegate.databaseURL = databaseURL;

もしかするとテンプレートの persistentContainer メソッドの様に _persistentContainer が nil の場合に初期化処理が走るのではなく、初期化処理をメソッドとして用意してやって、databaseURL の設定と共に初期化処理を明示的に実行させてやっても良いかも知れない。

サンプルコードの全体は以下。
(その他の AppDelegate の処理は割愛して Core Data の実装のみ記述)

Change PersistentContainer URL dynamically.

*1:例えばメモアプリのローカルデータとiCloud上のデータ

*2:DBの作りで1つのDBファイルで複数アカウントを管理する事も可能ではある

*3:開発中の話なので前回の方法+マクロで切り替えとかでも可能