CoreDataでハマったYO\(^o^)/

ちょっと前までMacrubyでMacアプリ(OSX)を作ろうと思ってたけど、 objective-cのソースをrubyに直したりもするから結局objective-c勉強せなあかんし、 Macrubyでハマるとこもあって、結局objective-cでアプリを作ってる。

多少物が出来てきて色々はまった(勉強した)点をブログに書こう思ってるけど、テンション上がらず中々筆が進まない今日この頃。 気合を入れて1筆取ろうかと。

テーマはCoreData。
ググると情報あるし、簡単に使うだけなら結構すぐにやれたけど、iOSじゃなくてOSX特有ではまった点があったのでそれを書いてみる。

ってか、ぐぐるとiOSの情報ばっか。Macネイティブアプリは寂しいね。(まぁ大抵の情報は共通だけど)

OSXでハマった点

作成したDBにsqliteで直接アクセスできない

OSXでCoreDataを使う設定で自動生成されたソースをベースにDBにデータを出し入れするソースを書いてみた。 それは普通に動くんだけど、作成されたDBをsqliteで直接参照できない!

デフォルトでDBが作成されるパスは

~/Library/Application Support/AppName/AppName.storedata

sqlite3 AppName.storedataで接続すると特にエラーなく接続できるのに.tablesとかすると

Error: file is encrypted or is not a database

ってエラーがでる。

objetive-cのプログラムでデータ入れるとデータファイルのファイルサイズはちゃんと増えてるからちゃんと登録されているはずなのになぜ!!iOSアプリだとちゃんとできるのに。

SQLデバッグログが出ない

SQLデバッグログを出力するには 起動オプションの引数に-com.apple.CoreData.SQLDebug 1を追加すればいいみたい。 具体的には、Scheme -> Edit Scheme... -> Run AppNameのArgumentタブのArguments Passed On Launchに追加する。

f:id:katz-lifehack:20130603180040p:plain

でも、ちゃんと設定してるはずなのにでない。こっちもiOSアプリならちゃんと出るのに...

って悩んでたけど、気が付くと解決は一瞬だった。

原因は衝撃的にくだらなかった...

OSX Applicationの方で自動生成されるCoreData周りのソースをずっと眺めてると

// Returns the persistent store coordinator for the application. This implementation creates and return a coordinator, having added the store for the application to it. (The directory for the store is created, if necessary.)
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
    if (_persistentStoreCoordinator) {
        return _persistentStoreCoordinator;
    }

    NSManagedObjectModel *mom = [self managedObjectModel];
    if (!mom) {
        NSLog(@"%@:%@ No model to generate a store from", [self class], NSStringFromSelector(_cmd));
        return nil;
    }

    NSFileManager *fileManager = [NSFileManager defaultManager];
    NSURL *applicationFilesDirectory = [self applicationFilesDirectory];
    NSError *error = nil;

    NSDictionary *properties = [applicationFilesDirectory resourceValuesForKeys:@[NSURLIsDirectoryKey] error:&error];

    if (!properties) {
        BOOL ok = NO;
        if ([error code] == NSFileReadNoSuchFileError) {
            ok = [fileManager createDirectoryAtPath:[applicationFilesDirectory path] withIntermediateDirectories:YES attributes:nil error:&error];
        }
        if (!ok) {
            [[NSApplication sharedApplication] presentError:error];
            return nil;
        }
    } else {
        if (![properties[NSURLIsDirectoryKey] boolValue]) {
            // Customize and localize this error.
            NSString *failureDescription = [NSString stringWithFormat:@"Expected a folder to store application data, found a file (%@).", [applicationFilesDirectory path]];

            NSMutableDictionary *dict = [NSMutableDictionary dictionary];
            [dict setValue:failureDescription forKey:NSLocalizedDescriptionKey];
            error = [NSError errorWithDomain:@"YOUR_ERROR_DOMAIN" code:101 userInfo:dict];

            [[NSApplication sharedApplication] presentError:error];
            return nil;
        }
    }

    NSURL *url = [applicationFilesDirectory URLByAppendingPathComponent:@"AppName.storedata"];
    NSPersistentStoreCoordinator *coordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:mom];
    if (![coordinator addPersistentStoreWithType:NSXMLStoreType configuration:nil URL:url options:nil error:&error]) {
        [[NSApplication sharedApplication] presentError:error];
        return nil;
    }
    _persistentStoreCoordinator = coordinator;

    return _persistentStoreCoordinator;
}

ん?

    NSURL *url = [applicationFilesDirectory URLByAppendingPathComponent:@"AppName.storedata"];
    NSPersistentStoreCoordinator *coordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:mom];
    if (![coordinator addPersistentStoreWithType:NSXMLStoreType configuration:nil URL:url options:nil error:&error]) {
        [[NSApplication sharedApplication] presentError:error];
        return nil;
    }
    _persistentStoreCoordinator = coordinator;

んんんんんんっっっっっっ?????

    if (![coordinator addPersistentStoreWithType:NSXMLStoreType configuration:nil URL:url options:nil error:&error]) {

StoreTypeがNSXMLStore!?!? iOS Applicationで自動生成されるソースでNSPersistentStoreCoordinatorを生成してる箇所を見てみると

_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
    NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
    abort();
}    

StoreTypeがNSSQLiteになっている!

これか!!!

デフォルトで作成されるソースはsqlite使ってないやん。XML使ってるやんwww AppName.sotredataをエディタで開いてみるとめっさXML...
がーん
そりゃログも出んわwww

NSSQLiteStoreでNSPersistentStoreCoordinatorを生成したら、普通にデバッグログ出る。
はー、CoreDataのガイドにSQLiteの他XMLとかのタイプがあるって書いてあるけど、完全にデフォルトSQLiteだと思ってた。はずい。

対応

こんな感じに直せばXMLじゃなくてSQLiteに変更でけた。

- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
    if (_persistentStoreCoordinator) {
        return _persistentStoreCoordinator;
    }

    NSManagedObjectModel *mom = [self managedObjectModel];
    if (!mom) {
        NSLog(@"%@:%@ No model to generate a store from", [self class], NSStringFromSelector(_cmd));
        return nil;
    }

    NSURL *applicationFilesDirectory = [self applicationFilesDirectory];
    NSError *error = nil;

    NSURL *url = [applicationFilesDirectory URLByAppendingPathComponent:@".sqlite"];
    NSPersistentStoreCoordinator *coordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:mom];
    if (![coordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:url options:nil error:&error]) {
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    }
    _persistentStoreCoordinator = coordinator;
    return _persistentStoreCoordinator;
}

にしても、出し入れのコードは変えなくてもXML /SQLiteを切り替えてくれるなんて。
自動生成でここまで作ってくれるなんてCoreData便利なのかな?
(まぁXMLなんてもう使わないだろうけど。iOSではそもそも使えないみたいだし。)