SwiftでCoreData

  • 271
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

概要

この記事は二つのテーマについて書きます。
『SwiftでCoreDataを使うときの注意点』 & 『超忙しい人のためのCoreData入門』。

フレームワークの使い方に重きを置いています。
Swiftの記法はむしろ得意ではないので、お気づきの点はご指摘頂けると嬉しいです。
※Xcode6.1

CoreDataとは

CoreDataはCocoaFrameworkに搭載されているO/Rマッパー。高速、且つメモリー消費を抑えて大量のデータを扱うことができます。
データベースはSqliteであればCoreDataの機能をフルに使うことができます。

『SwiftでCoreDataを使うときの注意点』

CoreDataを使うにあたって、Objective-Cとの相違点について。

二つの注意点

SwiftでCoreDataを実装する際の注意点。たった2つです。
@NSManaged名前空間です。

@NSManaged

Objective-Cの@dynamic属性がSwiftではなくなりました。
代わりに@NSManaged属性が追加されました。

@dynamic属性

CoreDataではエンティティを表すモデルクラス(NSManagedObjectのサブクラス)のプロパティに@dynamic属性を付加していました。
@dynamic属性は@synthesize属性と同様に、プロパティのアクセサを指定します。

属性 プロパティの実装
@synthesize  コンパイル時に生成
@dynamic 実行時に生成

と言えます。

CoreDataではフレームワークがプロパティを実行時に生成し、その実行効率はプログラマーが実装するよりも良いとされています。

@NSManaged属性

Swiftで追加された機能です。
実装時は@dynamicの単なる置き換えと考えて良いでしょう。やはり、実行時にプロパティの実装は実行時に生成されるようです。
唯一@dynamicと異なる点は、この属性がCoreData専用の属性であるということです。

名前空間

Swiftでは名前空間の仕組みが取り入れられました。Objective-Cまではプロジェクト毎にプレフィックスを作ることが推奨されていましたが、それに置き換わる仕組みと私は解釈しています。リファレンスによるとモジュール毎(概ねプロジェクト毎)に名前空間のスコープが作られるとか。
CoreDataにおいてはUtilitiesでEntityのクラスを指定する際に
スクリーンショット 2014-12-01 22.23.50.png
名前空間から始まるクラス名で記述しなければなりません。
名前空間はモジュール名…とリファレンスにありますが、ターゲット名がそれにあたるようです。

まとめ

Swiftでは@NSManaged属性を使いましょう。
Entityのクラス名は名前空間から書きましょう。



『超忙しい人のためのCoreData入門』

私がCoreDataに手を出した時、とても敷居が高く感じました。
登場するクラスが多かったり、クラス名が長くて・似ていて混乱しました。

私なりに、CoreDataに初めて触れる方がわかったつもりになれる記事を目指します。

登場するクラス

CoreDataを理解するにはフレームワークに用意されたクラスを知る必要があります。
1. NSPersistentStore
データベースの抽象化クラス
2. NSManagedObjectContext
エンティティモデルのお世話をするクラス
3. NSPersistentStoreCoordinator
NSPersistentStoreとNSManagedObjectContextの仲介クラス
4. NSManagedObjectModel
スキーマを表すクラス
5. NSManagedObject
エンティティクラス、モデルクラス、テーブルのレコード

主要なクラスは以上。
っと、いきなりですが、この入門では、1と3と4は忘れてください。
1. NSPersistentStore
データベースの抽象化クラス

2. NSManagedObjectContext
エンティティモデルのお世話をするクラス

3. NSPersistentStoreCoordinator
NSPersistentStoreとNSManagedObjectContextの仲介クラス

4. NSManagedObjectModel
スキーマを表すクラス

5. NSManagedObject
エンティティクラス、モデルクラス、テーブルのレコード

エンティティモデルのお世話をしてくれるNSManagedObjectContextを使って
エンティティモデルのNSManagedObjectを
作る(insert)
検索する(select)
変更する(update)
削除する(delete)
こんなイメージです。

この先はサンプルプロジェクトを作りながら追っていきます。

新規プロジェクト作成

「Use Core Data」にチェックを入れましょう。
1.png
すると、AppDelegateクラスにCoreDataに関するコードが自動生成されます。
先ほど忘れてくださいと言ったクラス達は、この自動生成されるコードで殆ど事足ります。

単一DB、単一スレッドでCoreDataを操作する場合は、変更の必要はほぼないでしょう。

また、3のエンティティモデルのお世話をしてくれるNSManagedObjectContextのゲッターまで自動生成されます。

残るはエンティティモデルのNSManagedObjectだけですね。

GUIでエンティティモデルを定義

プロジェクトに.xcdatamodeldという拡張子のファイルが初めから含まれているはずです。
スクリーンショット 2014-12-02 23.11.59.png
このファイルを選択するとエンティティモデルを定義する画面に変わります。
2.png
①で囲ったところでテーブル名を定義
②で囲ったところでテーブルのカラムを定義するイメージです。
ここではModelというテーブルにsomeDataAというカラム(型はString)と
someDataBというカラム(Int16)を追加しました。

GUIでエンティティモデルのコードを生成

エンティティモデルの定義を元にモデルクラスを生成します。
Editorメニューから…
pic3.png
はい、
5. NSManagedObject
エンティティクラス、モデルクラス、テーブルのレコード

このNSManagedObjectのサブクラスとして生成します。
エンティティ同士のリレーションシップが無い場合は自動生成のコードのままで問題ないでしょう。

import Foundation
import CoreData

class Model: NSManagedObject {

    @NSManaged var someDataA: String
    @NSManaged var someDataB: NSNumber

}

ここまででCoreDataを使うためのお膳立ては全て終了です。
まだコードを1行も書いてません。
GUIでエンティティモデルを定義して、モデルクラスを自動生成しただけです。
CoreDataなんて怖くない。

データ挿入

データ挿入のサンプルです。
NSEntityDescriptionというクラスが登場します。
このクラスはエンティティのモデルクラスの定義とインスタンスを管理するクラスです。

// AppDelegateクラスのインスタンスを取得        
let appDelegate = UIApplication.sharedApplication().delegate as AppDelegate
// AppDelegateクラスからNSManagedObjectContextを取得
// ゲッターはプロジェクト作成時に自動生成されている
if let managedObjectContext = appDelegate.managedObjectContext {
    // NSEntityDescriptionから新しいエンティティモデルのインスタンスを取得
    // 第一引数はモデルクラスの名前、第二引数はNSManagedObjectContext
    let managedObject: AnyObject = NSEntityDescription.insertNewObjectForEntityForName("Model", inManagedObjectContext: managedObjectContext)
    // エンティティモデルにデータをセット
    let model = managedObject as CoreDataSample.Model
    model.someDataA = "hogehoge"
    model.someDataB = 100

    // AppDelegateクラスに自動生成された saveContext で保存完了    appDelegate.saveContext()
}

データ検索

データの検索を行うサンプルです。
ここでも新しいクラスが登場します。
NSFetchRequestはSQLのSelect文、NSPredicateはSQLのWhere句と捉えるとイメージし易いと思います。

// AppDelegateクラスのインスタンスを取得        
let appDelegate = UIApplication.sharedApplication().delegate as AppDelegate
// AppDelegateクラスからNSManagedObjectContextを取得
// ゲッターはプロジェクト作成時に自動生成されている
if let managedObjectContext = appDelegate.managedObjectContext {
    // EntityDescriptionのインスタンスを生成
    let entityDiscription = NSEntityDescription.entityForName("Model", inManagedObjectContext: managedObjectContext);
    // NSFetchRequest SQLのSelect文のようなイメージ
    let fetchRequest = NSFetchRequest();
    fetchRequest.entity = entityDiscription;
    // NSPredicate SQLのWhere句のようなイメージ
    // someDataBプロパティが100のレコードを指定している
    let predicate = NSPredicate(format: "%K = %d", "someDataB", 100)
    fetchRequest.predicate = predicate

    var error: NSError? = nil;
    // フェッチリクエストの実行
    if var results = managedObjectContext.executeFetchRequest(fetchRequest, error: &error) {
        for managedObject in results {
            let model = managedObject as Model;
            println("String: \(model.someDataA), Number: \(model.someDataB)");
        }
    }
}

データの更新

データ更新のサンプルです。
someDataAプロパティが"hogehoge"のレコードを"hugahuga"に更新します。
処理は上のデータ検索と大きく変わりません。

// AppDelegateクラスのインスタンスを取得        
let appDelegate = UIApplication.sharedApplication().delegate as AppDelegate
// AppDelegateクラスからNSManagedObjectContextを取得
// ゲッターはプロジェクト作成時に自動生成されている
if let managedObjectContext = appDelegate.managedObjectContext {
    // EntityDescriptionのインスタンスを生成
    let entityDiscription = NSEntityDescription.entityForName("Model", inManagedObjectContext: managedObjectContext);
    // NSFetchRequest SQLのSelect文のようなイメージ
    let fetchRequest = NSFetchRequest();
    fetchRequest.entity = entityDiscription;
    // NSPredicate SQLのWhere句のようなイメージ
    let predicate = NSPredicate(format: "%K = %@", "someDataA", "hogehoge")
    fetchRequest.predicate = predicate

    var error: NSError? = nil;
    // フェッチリクエストの実行
    if var results = managedObjectContext.executeFetchRequest(fetchRequest, error: &error) {
        for managedObject in results {
            let model = managedObject as Model;

            // 
            // レコードの更新!
            // ここまではデータ検索のサンプルと同じ
            //
            model.someDataA = "hugahuga"
        }
    }
    // AppDelegateクラスに自動生成された saveContext で保存完了
    appDelegate.saveContext()
}

データの削除

データ更新のサンプルです。
someDataAプロパティが"hugahuga"のレコードを削除します。

// AppDelegateクラスのインスタンスを取得        
let appDelegate = UIApplication.sharedApplication().delegate as AppDelegate
// AppDelegateクラスからNSManagedObjectContextを取得
// ゲッターはプロジェクト作成時に自動生成されている
if let managedObjectContext = appDelegate.managedObjectContext {
    // EntityDescriptionのインスタンスを生成
    let entityDiscription = NSEntityDescription.entityForName("Model", inManagedObjectContext: managedObjectContext);
    // NSFetchRequest SQLのSelect文のようなイメージ
    let fetchRequest = NSFetchRequest();
    fetchRequest.entity = entityDiscription;
    // NSPredicate SQLのWhere句のようなイメージ
    let predicate = NSPredicate(format: "%K = %@", "someDataA", "hugahuga")
    fetchRequest.predicate = predicate

    var error: NSError? = nil;
    // フェッチリクエストの実行
    if var results = managedObjectContext.executeFetchRequest(fetchRequest, error: &error) {
        for managedObject in results {
            let model = managedObject as Model;
            //
            // レコード削除!
            //
            managedObjectContext.deleteObject(model);
        }
    }
    // AppDelegateクラスに自動生成された saveContext で保存完了
    appDelegate.saveContext()
}

カンのいい方はすでにお気づきでしょうが、NSManagedObjectクラス(このサンプルではModelクラス)のインスタンスを取得するまではデータの検索と同じ処理です。
インスタンスを取得したあとは、NSManagedObjectContext経由で変更を保存するか、削除するかの違いです。

この章、冒頭に述べた通り
エンティティモデルのお世話をしてくれるNSManagedObjectContextを使って
エンティティモデルのNSManagedObjectを
作る(insert)
検索する(select)
変更する(update)
削除する(delete)

これだけです。

おしまい

CoreData入門でした。入り口まではご案内できましたでしょうか。
必要最低限のインプットでCoreDataが操作できるコードを載せたつもりです。
新たにCoreDataをはじめる、足がかりにしていただけたら幸いです。

この投稿は Swift Advent Calendar 20145日目の記事です。