2011年8月17日水曜日

MacRuby から CoreData

アプリに入れるデータは MacRuby で作っていたりします。Objective-C でも良いんだけど、コンパイルとか面倒だし、テキスト処理は Ruby だと楽チンだし♪
#! /usr/local/bin/macruby
# coding:utf-8
#

framework 'Foundation'
framework 'CoreData'

DB_NAME = "Question"
ENTITY_NAME = "Question"

class DB
  def managedObjectModel
    if (@managedObjectModel != nil)
        return @managedObjectModel
    end
    modelURL = NSURL.URLWithString Dir.pwd+"/#{DB_NAME}.momd"
    @managedObjectModel = NSManagedObjectModel.alloc.initWithContentsOfURL(modelURL)   
    return @managedObjectModel
  end

  def persistentStoreCoordinator
    if (@persistentStoreCoordinator != nil)
        return @persistentStoreCoordinator
    end
    
    storeURL = NSURL.fileURLWithPath Dir.pwd+"/#{DB_NAME}.sqlite"
   
    error = Pointer.new(:object)
    @persistentStoreCoordinator = NSPersistentStoreCoordinator.alloc.initWithManagedObjectModel(self.managedObjectModel)
    if (!@persistentStoreCoordinator.addPersistentStoreWithType(NSSQLiteStoreType, configuration:nil, URL:storeURL, options:nil, error:error))
        puts "Unresolved error #{error}, #{error.userInfo}"
        abort()
    end
    
    return @persistentStoreCoordinator
    
  end

  def managedObjectContext
    if (@managedObjectContext != nil)
        return @managedObjectContext
    end
    
    coordinator = self.persistentStoreCoordinator
    if (coordinator != nil)
        @managedObjectContext = NSManagedObjectContext.alloc.init
        @managedObjectContext.setPersistentStoreCoordinator(coordinator)
    end
    return @managedObjectContext
  end



  def saveContext
    error = Pointer.new(:object)
    managedObjectContext = self.managedObjectContext
    if (managedObjectContext != nil)
        if (managedObjectContext.hasChanges || !managedObjectContext.save(error))
            puts "Unresolved error #{error}, #{error.userInfo}"
            abort()
        end
    end
  end
    
end

`/Developer/usr/bin/momc #{DB_NAME}.xcdatamodeld #{DB_NAME}.momd`
if File.exist? "#{DB_NAME}.sqlite"
  File.delete "#{DB_NAME}.sqlite"
end

db = DB.new
p model = db.managedObjectModel

var = {"YEAR" => 2010}
request = model.fetchRequestFromTemplateWithName("QuestionsWithYear", substitutionVariables:var)

p request.predicate.predicateFormat

db.saveContext

Xcode4 の Model Editor

Xcode には CoreData で操作するモデル作成する GUI エディタがある。こんなやつ。
画像の左に "Fetch Requests" という項目があるが、これは名前が示す通り NSFetchRequest を定義しておけるものだ。ソースコードに書くといろいろややこしく、見づらくなってしまうけど、モデルデータに格納しておけば比較的すっきりと書くことができる。
NSURL *modelURL = [NSURL URLWithString:path];
NSManagedObjectModel *managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
// モデルデータにある Fetch Request の一覧を取得
NSDictionary *requests = [managedObjectModel fetchRequestTemplatesByName];
// 名前を元に特定の Fetch Request を取得
NSFetchRequest *request = [managedObjectModel fetchRequestTemplateForName:@"MyFetchRequest"];
さて、この Fetch Request であるが、"year == 2011" などの定数を元に作成してもあまり恩恵はない。やはり変数を扱える方が嬉しい。Xcode3 までは Editor のポップアップに VARIABLE という項目があり、簡単に利用する事ができた。Xcode4 からはこの選択項目がなくなり、自分で記入しなければいけないので注意が必要。NSPredicate を使った事がある人なら分かると思うが、変数を扱うには $ 記号を使って変数名を指定してあげれば良い。この時、expression を選択しておくのがポイントだ。
このようにして作成した Fetch Request のテンプレートは fetchRequestTemplateForName: ではなく fetchRequestFromTemplateWithName:substitutionVariables: を使って呼び出す。
NSDictionary *variables = [NSDictionary dictionaryWithObject:[NSNumber numberWithInteger:2010] forKey:@"YEAR"];
NSFetchRequest *request = [managedObjectModel fetchRequestFromTemplateWithName:@"QuestionsWithYear" substitutionVariables:variables];

2011年8月16日火曜日

CoreData をちょっと説明する




概要

iOS 上でいくらか面倒な情報を保存、読み出ししなければ行けないような場合、NSUserDefaults や NSKeyedArchiver では直ぐに限界を感じてしまう。ファイルへの読み書きは速度が遅いし、何より特定の項目を検索できないのが辛い。そのような時のために、iOS では CoreData というデータベースを扱えるようになっている。

CoreData の大元

ファイル

CoreData はそのモデルの情報をファイルに格納している。 Xcode 上では .xcdatamodeld というフォーマットで、これは Xcode 上で使うための専用のフォーマットで、iPhone 側に渡す事はない。iPhone 上ではこれをコンパイルして .momd というフォーマットにして渡す。.momd はビルド時に作成されるが、自分で作りたい時は以下のようにしてファイルを作成する。
/Developer/usr/bin/momc Model.xcdatamodeld Model.momd
  
この .momd から .sqlite などの Store が作成されるので、大元の .xcdatamodeld に変更を加えなくて済むように事前によく設計した方が良い。簡単な変更ならバージョン管理機能でほとんどコードをいじらずにデータベースをバージョンアップできる。

クラス

NSManagedObjectModel
上記のモデルデータを扱うためのクラス。
NSPersistentStoreCoordinate
Store つまりDB本体を扱うためのクラス。NSManagedObjectModel をもとに DB を作成する。
NSManagedObjectContext
メモリ上のDBという感じのクラス。データの変更などはここに蓄えられ、アプリの終了時などにまとめて保存する。
データベースとのやり取りにおいて幾層にも抽象化されていて実態が分かりづらいけど、面倒な事はみんな CoreData がやってくれるのでコードに専念できる。
つづく...

2011年8月9日火曜日

テストデータの利用

テストデータを利用するために Bundle に含めるのだけど、[NSBundle mainBundle] だとテスト用の Bundle ではなくてアプリの方の Bundle を参照するのでよろしくない。
こういう時は、[NSBundle bundleForClass:[MyAppTests class]] などして Bundle を指定してあげるのが良さそうだ。
NSBundle *bundle = [NSBundle bundleForClass:[MyAppTests class]];

//Accurateness test
NSString *path = [bundle pathForResource:@"test_data" ofType:@"txt"];
NSString *str = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:NULL];
STAssertTrue([MyApp myMethod:str], @"myMethod");