【Swift UI】Core Dataの使い方!SQLiteにデータを永続的に保存する

この記事からわかること

  • Swift UICore Data利用する方法
  • SQLite使い方
  • .xcdatamodelファイル役割定義方法
  • NSPersistentContainerクラスとは?

index

[open]

\ アプリをリリースしました /

みんなの誕生日

友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-

posted withアプリーチ

公式リファレンス:Core Data

環境

Core Dataとは?

Core DataとはAppleが提供しているデータを永続的に保存、管理するためのフレームワークです。アプリ内のモデルオブジェクト(データ)をRDB(リレーショナルデータベース)で管理できる形式に変換してくれる役割があります。データベース自体の構築や付随するCRUD処理などもCore DataのAPIとして提供されており、またデータベースはデバイス内に保管されるためアプリが停止した際にもデータを保持し、再度アプリが起動した際にデータを利用できるようになります。

Core DataはバックエンドストアとしてSQLiteが活用されていますが、保存方法はカスタマイズ可能でメモリやバイナリファイルへ保存先を変更することも可能です。メモリに保存する場合はアプリ起動中のみ保持される一時的なデータになるので永続的にデータを保持したい場合はデフォルト設定のSQLiteなどを使用します。

Core Dataとは?〜まとめ〜

データを永続的に保存する他の方法

Swiftでアプリ内でデータを永続的に保存する方法は他にも存在します。

それぞれに特徴やメリットがありますが、Core Dataでは「データモデル定義の容易さとリレーションシップ」が大きなメリットだと思います。「Realm Swift」でも同じようなメリットがありますが、Core DataはApple公式のフレームワークであり、導入作業やバージョン管理が必要ないのも使い分けのポイントになると思います。

使い方

Core Dataを使用するためにはプロジェクト作成時に「Use Core Data」にチェックを入れる必要があります。

Xcodeの新規プロジェクト作成画面

チェックを入れた状態でプロジェクトを生成すると「プロジェクト名.xcdatamodel」と「Persistence.swift」が自動で生成されます。

プロジェクト名.xcdatamodel

「プロジェクト名.xcdatamodel」はCore Dataで利用するデータモデル(Entity)を定義するためのファイルです。エンティティーを追加するには下部にある「Add Entity」をクリックし、データに持たせたい属性(Attributes)を追加していくだけです。データベースのテーブルを作っていくようなイメージですね。

【Swift UI】Core Dataの使い方!SQLiteにデータを永続的に保存する

このファイルでエンティティー間のリレーションを定義することもできるようです。サンプルで自動生成時にItemが定義されています。

Persistence.swift

「Persistence.swift」はCore Dataのロジック部分が記述されているファイルです。これもチェックを入れることで自動生成されます。コードの役割や意味をコメントで付与しておきました。

import CoreData

struct PersistenceController {
    // シングルトン
    static let shared = PersistenceController(inMemory: true)

    // プレビュー用のデモデータ用(なくてもOK)
    static var preview: PersistenceController = {
        let result = PersistenceController(inMemory: true)
        let viewContext = result.container.viewContext
        
        for _ in 0..<10 {
            let newItem = Item(context: viewContext)
            newItem.timestamp = Date()
        }
        do {
            // データの追加
            try viewContext.save()
        } catch {
            let nsError = error as NSError
            fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
        }
        return result
    }()

    let container: NSPersistentContainer

    // 初期化&セットアップ処理」
    init(inMemory: Bool = false) {
        // .xcdatamodelファイル(モデルファイル名を渡す)
        // 永続的なコンテナー インスタンスを作成
        container = NSPersistentContainer(name: "CoreDataTest")
        
        if inMemory {
            // オンメモリで使用したい場合
            // /dev/nullはLinuxにおいてゴミ箱的な役割の特別なファイル
            print("InMemory")
            container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
        } else {
            // falseの場合は通常通りSQLiteファイルの保存する
            print("InFile")
        }
        // 永続ストアをロード ストアが存在しない場合は自動作成
        container.loadPersistentStores(completionHandler: { (storeDescription, error) in
            if let error = error as NSError? {
                fatalError("Unresolved error \(error), \(error.userInfo)")
            }
        })
        // 親コンテキストからの変更を自動的にマージする
        container.viewContext.automaticallyMergesChangesFromParent = true
    }
}

Swift UIでの実装

Swift UIでCore Dataを操作する場合は以下のようになります。これは「タイムスタンプをCore Data内で永続化して保存して表示するサンプルコード」です。

import SwiftUI
import CoreData

struct ContentView: View {
    // コンテキストの取得
    @Environment(\.managedObjectContext) private var viewContext
    
    // データ取得
    // データの変化とともにプロパティの値も自動で変化する
    @FetchRequest(
        sortDescriptors: [NSSortDescriptor(keyPath: \Item.timestamp, ascending: true)],
        animation: .default)
    private var items: FetchedResults
    
    var body: some View {
        NavigationView {
            List {
                ForEach(items) { item in
                    NavigationLink {
                        Text("Item at \(item.timestamp!, formatter: itemFormatter)")
                    } label: {
                        Text(item.timestamp!, formatter: itemFormatter)
                    }
                }
                .onDelete(perform: deleteItems)
            }
            .toolbar {
                ToolbarItem(placement: .navigationBarTrailing) {
                    EditButton()
                }
                ToolbarItem {
                    Button(action: addItem) {
                        Label("Add Item", systemImage: "plus")
                    }
                }
            }
            Text("Select an item")
        }
    }
    
    // データの追加処理
    /// エンティティをインスタンス化してデータを格納
    /// viewContext.save() で反映
    private func addItem() {
        withAnimation {
            let newItem = Item(context: viewContext)
            newItem.timestamp = Date()
            
            do {
                try viewContext.save()
            } catch {
                let nsError = error as NSError
                fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
            }
        }
    }
    
    // データの削除処理
    /// データを削除する
    /// viewContext.save() で反映
    private func deleteItems(offsets: IndexSet) {
        withAnimation {
            offsets.map { items[$0] }.forEach(viewContext.delete)
            
            do {
                try viewContext.save()
            } catch {
                let nsError = error as NSError
                fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
            }
        }
    }
}

private let itemFormatter: DateFormatter = {
    let formatter = DateFormatter()
    formatter.dateStyle = .short
    formatter.timeStyle = .medium
    return formatter
}()

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView().environment(\.managedObjectContext, PersistenceController.preview.container.viewContext)
    }
}

ご覧いただきありがとうございました。

searchbox

スポンサー

ProFile

ame

趣味:読書,プログラミング学習,サイト制作,ブログ

IT嫌いを克服するためにITパスを取得しようと勉強してからサイト制作が趣味に変わりました笑
今はCMSを使わずこのサイトを完全自作でサイト運営中〜

New Article

index