項目

  • はじめに
  • Core Dataとは
  • Core Dataのメリットとデメリット
  • 主要機能とコード例
  • 実装時の注意点
  • まとめ

1. はじめに

  • iOSアプリで複雑なデータをローカルに保存・管理する際、Apple純正のCore Dataは今なお多くの現場で必須となる強力なツールです。
  • 本記事では、SwiftUI環境でCore Dataを正しく使う方法について、基本的なCRUD操作(追加・取得・更新・削除)から実装時の注意点までをシンプルに解説します。

2. Core Dataとは

  • Core Dataはデータベースではありません。オブジェクトのライフサイクルや関係性(リレーションシップ)、データの整合性を管理するための「オブジェクトグラフ管理フレームワーク(Object Graph Manager)」です。
  • 永続化ストアへの保存はその機能の一部に過ぎず、デフォルトのストレージエンジンにはSQLiteが使われています。

3. Core Dataのメリットとデメリット

  • メリット:
    • 圧倒的なパフォーマンス:必要な時にだけデータを読み込む遅延読み込みにより、数万件のレコードを処理する場合でもRAMの消費を最小限に抑えられます。
    • 強力なマイグレーション機能:エンティティの属性を追加・変更する際は、自動マイグレーションから高度なカスタムマイグレーションまで、スムーズなデータ移行の手段が用意されています。
    • 成熟したエコシステム:バグによるクラッシュが少なく、コミュニティの知見も豊富で、古いiOSバージョンでも安定して動作します。
    • CloudKit連携:わずか数行の設定で、ユーザーのiCloudとデータを簡単に同期できます。
  • デメリット:
    • スレッドセーフではない(最重要):バックグラウンドスレッドで作成したオブジェクトを、そのままメインスレッド(UI表示用)に渡すことはできません。スレッドルールを破ると、データ破壊や再現しにくいクラッシュなど未定義の挙動を引き起こします(必ずしも即座に落ちるとは限らないため厄介です)。
    • ボイラープレートコードが多い:Realmなどのサードパーティ製ライブラリや新しいSwiftDataに比べ、初期設定に必要なコード量が多くなります。

4. 主要機能とコード例

  • 今回は、シンプルな本管理(Book Management)アプリを例に、Core Dataの動かし方を解説します。
  • セットアップと初期化(The Core Data Stack)
    • まず、Xcode内でデータモデルファイル(例:BookModel.xcdatamodeld)を作成します。
    • Book という名前の Entity を作成します。
    • Attributes を追加:title(String)、author(String)、timestamp(Date)
  • Core Data Stackを管理するシングルトンクラスを作成します。

import CoreData

class CoreDataManager {
    static let shared = CoreDataManager()
    
    // Core Data Stack全体を保持するコンテナ
    let persistentContainer: NSPersistentContainer
    
    // データ操作を行うための文脈(UI更新のため、デフォルトはメインスレッドで動作)
    var viewContext: NSManagedObjectContext {
        return persistentContainer.viewContext
    }
    
    private init() {
        persistentContainer = NSPersistentContainer(name: "BookModel") // .xcdatamodeldのファイル名と一致させる
        persistentContainer.loadPersistentStores { _, error in
            if let error = error as NSError? {
              
              // 学習用に fatalError を使っているが、本番ではエラーをログ送信・ユーザー通知・リカバリに回すべき
                fatalError("""
                Core Dataの初期化エラー
                Error: \(error)
                UserInfo: \(error.userInfo)
                """)
              
            }
        }
        // 別スレッドからのデータ変更を自動的にマージする設定
        persistentContainer.viewContext.automaticallyMergesChangesFromParent = true
    }
    
    // データを保存するための共通関数
    func saveContext() {
        let context = viewContext
        if context.hasChanges {
            do {
                try context.save()
            } catch {
                context.rollback()
                let nserror = error as NSError
                print("Context保存エラー: \(nserror), \(nserror.userInfo)")
            }
        }
    }
}
  • データの取得(Query / Fetch Request)
    SwiftUIにおいて、継続的にデータを取得し、変更時にUIを自動更新する最適な方法は、プロパティラッパー @FetchRequest を使用することです。

import SwiftUI
import CoreData

struct BookListView: View {

    @Environment(\.managedObjectContext)
    private var viewContext
    @FetchRequest(sortDescriptors: [SortDescriptor(\Book.timestamp, order: .forward)])
    private var books: FetchedResults<Book>

    @State private var viewModel = BookViewModel()
    @State private var showingAddBook = false

    var body: some View {
        NavigationStack {
            List {
              ForEach(books, id: \.objectID) { book in
                    NavigationLink {
                      // 編集画面
                        EditBookView(
                            book: book,
                            viewModel: viewModel
                        )
                    } label: {
                        VStack(alignment: .leading) {
                            Text(book.title ?? "タイトル不明")
                                .font(.headline)
                
                            Text(book.author ?? "著者不明")
                                .font(.subheadline)
                                .foregroundColor(.gray)
                        }
                    }
                }
                .onDelete { offsets in
                   viewModel.deleteBook(offsets.map { books[$0] })
                }
            }
            .navigationTitle("マイ本棚")
            .toolbar {
               // 追加画面
                ToolbarItem(placement: .topBarTrailing) {
                    Button {
                        showingAddBook = true
                    } label: {
                        Image(systemName: "plus")
                    }
                }
            }
            .sheet(isPresented: $showingAddBook) {
                AddBookView(viewModel: viewModel)
            }
        }
    }
}


  • 追加画面

import SwiftUI

struct AddBookView: View {

    @Environment(\.dismiss)
    private var dismiss

    var viewModel: BookViewModel

    @State private var title = ""
    @State private var author = ""

    var body: some View {
        NavigationStack {
            Form {
                TextField("タイトル", text: $title)
                TextField("著者", text: $author)
            }
            .navigationTitle("本を追加")
            .toolbar {

                ToolbarItem(placement: .cancellationAction) {
                    Button("キャンセル") {
                        dismiss()
                    }
                }

                ToolbarItem(placement: .confirmationAction) {
                    Button("保存") {

                        guard !title.isEmpty,
                              !author.isEmpty else {
                            return
                        }

                        viewModel.addBook(
                            title: title,
                            author: author
                        )

                        dismiss()
                    }
                }
            }
        }
    }
}


  • 編集画面

import SwiftUI

struct EditBookView: View {

    @Environment(\.dismiss)
    private var dismiss

    let book: Book
    var viewModel: BookViewModel

    @State private var title = ""
    @State private var author = ""

    var body: some View {
        Form {
            TextField("タイトル", text: $title)
            TextField("著者", text: $author)
        }
        .navigationTitle("本を編集")
        .onAppear {
            title = book.title ?? ""
            author = book.author ?? ""
        }
        .toolbar {
            Button("保存") {
                viewModel.updateBook(
                    book: book,
                    newTitle: title,
                    newAuthor: author
                )

                dismiss()
            }
        }
    }
}


  • データの追加・更新・削除

import Foundation
import CoreData
import Observation

@Observable
class BookViewModel {
    private let context = CoreDataManager.shared.viewContext


  //  新規追加
  func addBook(title: String, author: String) {
        let book = Book(context: context)
        book.title = title
        book.author = author
        book.timestamp = Date()

        CoreDataManager.shared.saveContext()
    }

  // データの更新
    func updateBook(book: Book, newTitle: String, newAuthor: String) {
        book.title = newTitle
        book.author = newAuthor

        CoreDataManager.shared.saveContext()
    }

  // データの削除
    func deleteBook(_ books: [Book]) {
        books.forEach(context.delete)

        CoreDataManager.shared.saveContext()
    }
}


5. 実装時の注意点

Core Dataをプロダクション環境に導入する際は、アプリのクラッシュやメモリリークを防ぐために、以下の点にご注意ください。

  • 環境(Environment)へのContextの注入:SwiftUIでは、アプリの起点となるファイル(App準拠のstruct)で、viewContextを環境にインジェクションする必要があります。これを行わないと、@FetchRequest が動作しません。

@main
struct MyApp: App {
    let coreDataManager = CoreDataManager.shared

    var body: some Scene {
        WindowGroup {
           BookListView()
            .environment(\.managedObjectContext, coreDataManager.viewContext)
       }
    }
}


  • マルチスレッドのルールを徹底する:
    • viewContextはメインスレッド専用です。UI描画のための軽量な処理にのみ使用してください。
    • APIから1万件のデータをダウンロードして一括保存する場合などは、絶対にviewContextを使わないでください。UIフリーズ(ラグ)を防ぐため、persistentContainer.performBackgroundTask を使用してバックグラウンド用のContextを作成し、非同期で処理します。
  • 開発時はデバッグフラグを有効にする:
    • スレッド間の衝突(マルチスレッド違反)を早期に検知するために、Xcodeの以下の設定を追加します。
      Xcode -> Product -> Scheme -> Edit Scheme -> Run -> Arguments -> Arguments Passed On Launch
    • ここに以下のフラグを追加します。
      -com.apple.CoreData.ConcurrencyDebug 1
    • これを有効にすると、スレッドルールに違反したコードが走った瞬間、アプリが該当の行で即座にクラッシュしてくれるため、バグを早期に発見できます。

6. まとめ

  • Core Dataは、最新のフレームワークに比べると構文が古く、複雑に見えるかもしれません。しかし、大量のデータを安全に処理するための強力なツールであり、iOSデベロッパーとしての実力を証明する「必須スキル」です。
  • Core Data Stackの仕組みと、マルチスレッドの絶対原則さえマスターすれば、このフレームワークを完全にコントロールできるようになります。Core Dataの基礎をしっかり固めておけば、将来的にSwiftDataへ移行する際もスムーズです。裏側の設計思想が同じであるため、無理なく理解できるはずです。



ギャップロを運営しているアップフロンティア株式会社では、一緒に働いてくれる仲間を随時、募集しています。 興味がある!一緒に働いてみたい!という方は下記よりご応募お待ちしております。
採用情報をみる