2016年2月6日土曜日

CoreDataでdate型のattributeを使用して比較を行う

ご無沙汰しています。はざまです。新しく記事を書くのは一体何ヶ月ぶりでしょうか・・・(約1年ぶりみたいですね・・・)
さて、前置きはこのぐらいにして、本題に入りましょう。

つい先日から、Swiftを使用してiOSアプリ作成を始めたのですが、CoreDataを使用してdate型の値を扱おうとした時に少しハマった点があったので、ここにメモ代わりとして残しておこうと思います。
 以下、CoreDataの基礎は理解しているものとして記載します。

例えば、CoreDataで以下のような構造のデータを管理しているとします。
id int32,
name string,
price int32
date date

この構造のデータベースから今月追加した分の商品の情報を全件取得したいとします。CoreDataのAPIを使用すれば、次のように記述すれば目的は達成できます。

let context = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
let request = NSFetchRequest()
let entity = NSEntityDescription.entityForName("Items", inManagedObjectContext: context)
request.entity = entity

let (this_month, next_month) = self.calculateFirstDayInMonthAndNext()
request.predicate = NSPredicate(format: "%@ <= date AND date < %@", argumentArray: [this_month, next_month])

do {
    let results = try context.executeFetchRequest(request)
...

しかし、実はこの書き方には落とし穴があります。XCodeの標準機能を使用してCoreDataのエンティティに対してモデルクラスを生成すると、date型はNSTimeInterval型としてマッピングされます。CoreDataは内部データストレージとしてSQLiteを使用しています。このSQLiteにはMySQLのdatetime型に相当する型が存在しないため、CoreDataのAPIは、NSDateに代表される日付型を、単純な基準日時からの経過時間に変換して保存します。この時、timeIntervalSinceReferenceDateプロパティを使用して変換を行うため、データの保存時にもこのプロパティを使用して変換していなければ齟齬が発生してしまい、条件にひっかからなくなってしまいます。

なお、どうしてもtimeIntervalSinceReferenceDateを使用してdate attributeの中身を上書きできない場合には、NSPredicateの中身をdate.timeIntervalSince1970などと書き換えることで、適当なNSDate型のプロパティを使用して条件指定できるようになります。既にリリース済みのアプリなどで、容易にデータ再構築ができない場合には一考する価値がありそうです。

0 件のコメント:

コメントを投稿

なにか意見や感想、質問などがあれば、ご自由にお書きください。