Core Data

Core Dataは、Appleが提供するオブジェクトグラフ管理と永続化を行うフレームワークです。iOSやmacOS開発において長年の実績を誇り、データを直接操作するのではなく、オブジェクトとして扱うことで複雑なデータ管理を抽象化します。NSManagedObjectContextを中心とした強力なメモリ管理、リレーションシップ処理、遅延読み込み、自動的な変更追跡機能を提供し、企業レベルのアプリケーション開発に必要な信頼性とパフォーマンスを実現しています。

ORMSwiftiOSmacOSオブジェクトグラフ永続化Apple

GitHub概要

swiftlang/swift-corelibs-foundation

The Foundation Project, providing core utilities, internationalization, and OS independence

スター5,374
ウォッチ312
フォーク1,157
作成日:2015年11月9日
言語:C
ライセンス:Apache License 2.0

トピックス

なし

スター履歴

swiftlang/swift-corelibs-foundation Star History
データ取得日時: 2025/7/19 08:07

ライブラリ

Core Data

概要

Core Dataは、Appleが提供するオブジェクトグラフ管理と永続化を行うフレームワークです。iOSやmacOS開発において長年の実績を誇り、データを直接操作するのではなく、オブジェクトとして扱うことで複雑なデータ管理を抽象化します。NSManagedObjectContextを中心とした強力なメモリ管理、リレーションシップ処理、遅延読み込み、自動的な変更追跡機能を提供し、企業レベルのアプリケーション開発に必要な信頼性とパフォーマンスを実現しています。

詳細

Core Data 2025年版は、SwiftDataの登場後も企業アプリケーション開発において確固たる地位を維持し続けています。iOS 10以降に導入されたNSPersistentContainerにより、Core Dataスタックの管理が大幅に簡素化され、現代的なSwift開発パターンとの統合が向上しました。SwiftUIとの完全統合により、@FetchRequest、@ObservedObject、環境変数を活用した宣言的なデータバインディングが可能になっています。CloudKitとの自動同期、バックグラウンドコンテキスト処理、バッチ操作、SQLiteストアの最適化など、大規模データ処理に必要な高度な機能を包括的にサポートしています。

主な特徴

  • オブジェクトグラフ管理: エンティティ間のリレーションシップと依存関係の自動管理
  • 遅延読み込み: 必要なタイミングでのデータロードによるメモリ効率性
  • 自動変更追跡: オブジェクトの変更を自動検出し、必要な更新のみ実行
  • バックグラウンド処理: メインスレッドをブロックしない非同期データ操作
  • CloudKit統合: デバイス間での自動データ同期とクラウドバックアップ
  • SwiftUI統合: 宣言的UIとのシームレスなデータバインディング

メリット・デメリット

メリット

  • Apple公式フレームワークによる長期サポートと安定性の保証
  • 複雑なオブジェクトグラフとリレーションシップの自動管理による開発効率向上
  • NSPersistentContainerによる簡潔なセットアップと管理
  • CloudKit統合による簡単なクラウド同期とマルチデバイス対応
  • SwiftUIとの完全統合による現代的な開発体験
  • 豊富なドキュメントとコミュニティサポート
  • エンタープライズレベルのパフォーマンスと信頼性

デメリット

  • 学習コストが高く、初心者には複雑なアーキテクチャ
  • Appleプラットフォームのみでクロスプラットフォーム開発に不向き
  • SwiftDataと比較して記述量が多く、ボイラープレートコードが必要
  • デバッグが困難で、パフォーマンス問題の特定に時間がかかる場合がある
  • データモデルの変更時にマイグレーション作業が複雑
  • 小規模プロジェクトには機能過多で設計の複雑化を招く可能性

参考ページ

書き方の例

プロジェクトセットアップと基本設定

// AppDelegate.swift または App.swift
import UIKit
import CoreData

@main
class AppDelegate: UIResponder, UIApplicationDelegate {
    
    // MARK: - Core Data Stack
    lazy var persistentContainer: NSPersistentContainer = {
        let container = NSPersistentContainer(name: "DataModel")
        container.loadPersistentStores(completionHandler: { (storeDescription, error) in
            if let error = error as NSError? {
                // 本番環境では適切なエラーハンドリングを実装
                fatalError("Core Data error: \(error), \(error.userInfo)")
            }
        })
        return container
    }()
    
    // MARK: - Core Data Saving Support
    func saveContext() {
        let context = persistentContainer.viewContext
        
        if context.hasChanges {
            do {
                try context.save()
            } catch {
                let nsError = error as NSError
                fatalError("Core Data save error: \(nsError), \(nsError.userInfo)")
            }
        }
    }
}

// SwiftUI App使用時
@main
struct MyApp: App {
    let persistenceController = PersistenceController.shared
    
    var body: some Scene {
        WindowGroup {
            ContentView()
                .environment(\.managedObjectContext, persistenceController.container.viewContext)
        }
    }
}

// PersistenceController.swift
import CoreData

struct PersistenceController {
    static let shared = PersistenceController()
    
    let container: NSPersistentContainer
    
    init(inMemory: Bool = false) {
        container = NSPersistentContainer(name: "DataModel")
        
        if inMemory {
            container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
        }
        
        container.loadPersistentStores { _, error in
            if let error = error as NSError? {
                fatalError("Core Data error: \(error), \(error.userInfo)")
            }
        }
        
        // 自動保存設定
        container.viewContext.automaticallyMergesChangesFromParent = true
    }
}

データモデル定義とエンティティ作成

// User+CoreDataClass.swift
import Foundation
import CoreData

@objc(User)
public class User: NSManagedObject {
    
    convenience init(context: NSManagedObjectContext, name: String, email: String) {
        self.init(context: context)
        self.name = name
        self.email = email
        self.createdAt = Date()
    }
    
    // 計算プロパティの例
    var displayName: String {
        return name ?? "Unknown User"
    }
    
    // バリデーション
    public override func validateForInsert() throws {
        try super.validateForInsert()
        try validateUserData()
    }
    
    public override func validateForUpdate() throws {
        try super.validateForUpdate()
        try validateUserData()
    }
    
    private func validateUserData() throws {
        guard let name = name, !name.isEmpty else {
            throw ValidationError.emptyName
        }
        
        guard let email = email, email.contains("@") else {
            throw ValidationError.invalidEmail
        }
    }
}

// User+CoreDataProperties.swift
import Foundation
import CoreData

extension User {
    
    @nonobjc public class func fetchRequest() -> NSFetchRequest<User> {
        return NSFetchRequest<User>(entityName: "User")
    }
    
    @NSManaged public var name: String?
    @NSManaged public var email: String?
    @NSManaged public var createdAt: Date?
    @NSManaged public var posts: NSSet?
}

// MARK: - Generated accessors for posts
extension User {
    
    @objc(addPostsObject:)
    @NSManaged public func addToPosts(_ value: Post)
    
    @objc(removePostsObject:)
    @NSManaged public func removeFromPosts(_ value: Post)
    
    @objc(addPosts:)
    @NSManaged public func addToPosts(_ values: NSSet)
    
    @objc(removePosts:)
    @NSManaged public func removeFromPosts(_ values: NSSet)
}

// ValidationError.swift
enum ValidationError: Error, LocalizedError {
    case emptyName
    case invalidEmail
    
    var errorDescription: String? {
        switch self {
        case .emptyName:
            return "名前は必須項目です"
        case .invalidEmail:
            return "有効なメールアドレスを入力してください"
        }
    }
}

CRUD操作とクエリ実行

// CoreDataManager.swift
import Foundation
import CoreData

class CoreDataManager {
    static let shared = CoreDataManager()
    
    private let persistentContainer: NSPersistentContainer
    
    private init() {
        persistentContainer = PersistenceController.shared.container
    }
    
    var context: NSManagedObjectContext {
        return persistentContainer.viewContext
    }
    
    // MARK: - Create
    func createUser(name: String, email: String) -> User {
        let user = User(context: context, name: name, email: email)
        saveContext()
        return user
    }
    
    func createPost(title: String, content: String, author: User) -> Post {
        let post = Post(context: context)
        post.title = title
        post.content = content
        post.author = author
        post.createdAt = Date()
        saveContext()
        return post
    }
    
    // MARK: - Read
    func fetchAllUsers() -> [User] {
        let request: NSFetchRequest<User> = User.fetchRequest()
        request.sortDescriptors = [NSSortDescriptor(key: "name", ascending: true)]
        
        do {
            return try context.fetch(request)
        } catch {
            print("ユーザー取得エラー: \(error)")
            return []
        }
    }
    
    func fetchUsers(matching predicate: NSPredicate, limit: Int? = nil) -> [User] {
        let request: NSFetchRequest<User> = User.fetchRequest()
        request.predicate = predicate
        request.sortDescriptors = [NSSortDescriptor(key: "createdAt", ascending: false)]
        
        if let limit = limit {
            request.fetchLimit = limit
        }
        
        do {
            return try context.fetch(request)
        } catch {
            print("条件付きユーザー取得エラー: \(error)")
            return []
        }
    }
    
    func fetchUserByEmail(_ email: String) -> User? {
        let predicate = NSPredicate(format: "email == %@", email)
        return fetchUsers(matching: predicate, limit: 1).first
    }
    
    // MARK: - Update
    func updateUser(_ user: User, name: String? = nil, email: String? = nil) {
        if let name = name {
            user.name = name
        }
        if let email = email {
            user.email = email
        }
        saveContext()
    }
    
    // MARK: - Delete
    func deleteUser(_ user: User) {
        context.delete(user)
        saveContext()
    }
    
    func deleteAllUsers() {
        let request: NSFetchRequest<NSFetchRequestResult> = User.fetchRequest()
        let deleteRequest = NSBatchDeleteRequest(fetchRequest: request)
        
        do {
            try context.execute(deleteRequest)
            try context.save()
        } catch {
            print("一括削除エラー: \(error)")
        }
    }
    
    // MARK: - Save Context
    func saveContext() {
        if context.hasChanges {
            do {
                try context.save()
            } catch {
                print("保存エラー: \(error)")
            }
        }
    }
}

// 使用例
let manager = CoreDataManager.shared

// ユーザー作成
let user = manager.createUser(name: "田中太郎", email: "[email protected]")

// 全ユーザー取得
let allUsers = manager.fetchAllUsers()

// 条件付き検索
let predicate = NSPredicate(format: "name CONTAINS[cd] %@", "田中")
let searchResults = manager.fetchUsers(matching: predicate)

// メールアドレスで検索
if let foundUser = manager.fetchUserByEmail("[email protected]") {
    print("ユーザーが見つかりました: \(foundUser.displayName)")
}

// ユーザー更新
manager.updateUser(user, name: "田中次郎")

// ユーザー削除
manager.deleteUser(user)

SwiftUI統合とリアクティブデータバインディング

// ContentView.swift
import SwiftUI
import CoreData

struct ContentView: View {
    @Environment(\.managedObjectContext) private var viewContext
    
    @FetchRequest(
        entity: User.entity(),
        sortDescriptors: [NSSortDescriptor(keyPath: \User.name, ascending: true)],
        predicate: nil
    ) private var users: FetchedResults<User>
    
    @State private var showingAddUser = false
    @State private var searchText = ""
    
    var filteredUsers: [User] {
        if searchText.isEmpty {
            return Array(users)
        } else {
            return users.filter { user in
                user.name?.localizedCaseInsensitiveContains(searchText) ?? false
            }
        }
    }
    
    var body: some View {
        NavigationView {
            List {
                ForEach(filteredUsers, id: \.objectID) { user in
                    UserRowView(user: user)
                }
                .onDelete(perform: deleteUsers)
            }
            .searchable(text: $searchText, prompt: "ユーザーを検索")
            .navigationTitle("ユーザー一覧")
            .toolbar {
                ToolbarItem(placement: .navigationBarTrailing) {
                    Button("追加") {
                        showingAddUser = true
                    }
                }
            }
            .sheet(isPresented: $showingAddUser) {
                AddUserView()
                    .environment(\.managedObjectContext, viewContext)
            }
        }
    }
    
    private func deleteUsers(offsets: IndexSet) {
        withAnimation {
            offsets.map { filteredUsers[$0] }.forEach(viewContext.delete)
            
            do {
                try viewContext.save()
            } catch {
                print("削除エラー: \(error)")
            }
        }
    }
}

// UserRowView.swift
struct UserRowView: View {
    @ObservedObject var user: User
    
    var body: some View {
        VStack(alignment: .leading, spacing: 4) {
            Text(user.displayName)
                .font(.headline)
            
            if let email = user.email {
                Text(email)
                    .font(.caption)
                    .foregroundColor(.secondary)
            }
            
            if let createdAt = user.createdAt {
                Text("作成日: \(createdAt, formatter: dateFormatter)")
                    .font(.caption2)
                    .foregroundColor(.secondary)
            }
        }
        .padding(.vertical, 2)
    }
    
    private let dateFormatter: DateFormatter = {
        let formatter = DateFormatter()
        formatter.dateStyle = .short
        formatter.timeStyle = .short
        return formatter
    }()
}

// AddUserView.swift
struct AddUserView: View {
    @Environment(\.managedObjectContext) private var viewContext
    @Environment(\.dismiss) private var dismiss
    
    @State private var name = ""
    @State private var email = ""
    @State private var showingAlert = false
    @State private var alertMessage = ""
    
    var body: some View {
        NavigationView {
            Form {
                Section(header: Text("ユーザー情報")) {
                    TextField("名前", text: $name)
                    TextField("メールアドレス", text: $email)
                        .keyboardType(.emailAddress)
                        .autocapitalization(.none)
                }
            }
            .navigationTitle("新規ユーザー")
            .navigationBarTitleDisplayMode(.inline)
            .toolbar {
                ToolbarItem(placement: .navigationBarLeading) {
                    Button("キャンセル") {
                        dismiss()
                    }
                }
                
                ToolbarItem(placement: .navigationBarTrailing) {
                    Button("保存") {
                        saveUser()
                    }
                    .disabled(name.isEmpty || email.isEmpty)
                }
            }
            .alert("エラー", isPresented: $showingAlert) {
                Button("OK") { }
            } message: {
                Text(alertMessage)
            }
        }
    }
    
    private func saveUser() {
        do {
            let newUser = User(context: viewContext, name: name, email: email)
            try viewContext.save()
            dismiss()
        } catch {
            alertMessage = "ユーザーの保存に失敗しました: \(error.localizedDescription)"
            showingAlert = true
        }
    }
}

バックグラウンド処理とパフォーマンス最適化

// BackgroundTaskManager.swift
import Foundation
import CoreData

class BackgroundTaskManager {
    private let persistentContainer: NSPersistentContainer
    
    init(container: NSPersistentContainer) {
        self.persistentContainer = container
    }
    
    // バックグラウンドコンテキストでの大量データ処理
    func performBackgroundTask(_ block: @escaping (NSManagedObjectContext) -> Void) {
        persistentContainer.performBackgroundTask { context in
            // バックグラウンドコンテキストの設定
            context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
            
            block(context)
            
            // バックグラウンドコンテキストでの保存
            if context.hasChanges {
                do {
                    try context.save()
                } catch {
                    print("バックグラウンド保存エラー: \(error)")
                }
            }
        }
    }
    
    // 大量データのバッチ処理
    func batchInsertUsers(userData: [[String: Any]], completion: @escaping (Result<Void, Error>) -> Void) {
        performBackgroundTask { context in
            let batchInsert = NSBatchInsertRequest(entity: User.entity()) { (managedObject: NSManagedObject) -> Bool in
                guard !userData.isEmpty else { return true }
                
                let userDict = userData.removeFirst()
                managedObject.setValue(userDict["name"], forKey: "name")
                managedObject.setValue(userDict["email"], forKey: "email")
                managedObject.setValue(Date(), forKey: "createdAt")
                
                return userData.isEmpty
            }
            
            do {
                try context.execute(batchInsert)
                DispatchQueue.main.async {
                    completion(.success(()))
                }
            } catch {
                DispatchQueue.main.async {
                    completion(.failure(error))
                }
            }
        }
    }
    
    // バッチ更新
    func batchUpdateUsers(predicate: NSPredicate, updates: [String: Any]) {
        performBackgroundTask { context in
            let batchUpdate = NSBatchUpdateRequest(entityName: "User")
            batchUpdate.predicate = predicate
            batchUpdate.propertiesToUpdate = updates
            batchUpdate.resultType = .updatedObjectsCountResultType
            
            do {
                let result = try context.execute(batchUpdate) as? NSBatchUpdateResult
                print("更新されたオブジェクト数: \(result?.result ?? 0)")
            } catch {
                print("バッチ更新エラー: \(error)")
            }
        }
    }
    
    // メモリ効率的なフェッチ
    func fetchUsersEfficiently(batchSize: Int = 20, completion: @escaping ([User]) -> Void) {
        performBackgroundTask { context in
            let request: NSFetchRequest<User> = User.fetchRequest()
            request.fetchBatchSize = batchSize
            request.returnsObjectsAsFaults = false
            
            do {
                let users = try context.fetch(request)
                DispatchQueue.main.async {
                    completion(users)
                }
            } catch {
                print("効率的フェッチエラー: \(error)")
                DispatchQueue.main.async {
                    completion([])
                }
            }
        }
    }
}

// 使用例
let backgroundManager = BackgroundTaskManager(container: persistentContainer)

// 大量データの挿入
let userData = [
    ["name": "ユーザー1", "email": "[email protected]"],
    ["name": "ユーザー2", "email": "[email protected]"],
    // ... 他のユーザーデータ
]

backgroundManager.batchInsertUsers(userData: userData) { result in
    switch result {
    case .success:
        print("バッチ挿入完了")
    case .failure(let error):
        print("バッチ挿入エラー: \(error)")
    }
}

// 条件付きバッチ更新
let predicate = NSPredicate(format: "createdAt < %@", Date().addingTimeInterval(-86400)) // 1日前
backgroundManager.batchUpdateUsers(predicate: predicate, updates: ["isActive": false])

CloudKit統合とデータ同期

// CloudKitManager.swift
import Foundation
import CoreData
import CloudKit

class CloudKitManager {
    private let container: NSPersistentCloudKitContainer
    
    init(container: NSPersistentCloudKitContainer) {
        self.container = container
        setupCloudKit()
    }
    
    private func setupCloudKit() {
        // CloudKit通知の設定
        container.persistentStoreDescriptions.forEach { description in
            description.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
            description.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
        }
    }
    
    // CloudKit同期状態の確認
    func checkCloudKitStatus() {
        CKContainer.default().accountStatus { [weak self] status, error in
            DispatchQueue.main.async {
                switch status {
                case .available:
                    print("CloudKitアカウント利用可能")
                    self?.enableCloudKitSync()
                case .noAccount:
                    print("iCloudアカウントなし")
                case .restricted:
                    print("CloudKitアクセス制限")
                case .couldNotDetermine:
                    print("CloudKit状態不明")
                case .temporarilyUnavailable:
                    print("CloudKit一時的に利用不可")
                @unknown default:
                    print("CloudKit未知の状態")
                }
            }
        }
    }
    
    private func enableCloudKitSync() {
        // CloudKit同期の有効化
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(contextDidSave),
            name: .NSManagedObjectContextDidSave,
            object: nil
        )
    }
    
    @objc private func contextDidSave(_ notification: Notification) {
        // CloudKit同期後の処理
        print("コンテキストが保存されました - CloudKit同期中")
    }
    
    // 手動同期の実行
    func forceSyncWithCloudKit() {
        container.performBackgroundTask { context in
            do {
                try context.save()
                print("CloudKitとの手動同期完了")
            } catch {
                print("CloudKit同期エラー: \(error)")
            }
        }
    }
    
    // CloudKitエラーハンドリング
    func handleCloudKitError(_ error: Error) {
        if let ckError = error as? CKError {
            switch ckError.code {
            case .quotaExceeded:
                print("CloudKitストレージ制限超過")
            case .networkUnavailable:
                print("ネットワーク接続なし")
            case .serviceUnavailable:
                print("CloudKitサービス利用不可")
            default:
                print("CloudKitエラー: \(ckError.localizedDescription)")
            }
        }
    }
}

// PersistenceController.swift(CloudKit対応版)
struct CloudKitPersistenceController {
    static let shared = CloudKitPersistenceController()
    
    let container: NSPersistentCloudKitContainer
    let cloudKitManager: CloudKitManager
    
    init(inMemory: Bool = false) {
        container = NSPersistentCloudKitContainer(name: "DataModel")
        
        if inMemory {
            container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
        } else {
            // CloudKit設定
            let storeDescription = container.persistentStoreDescriptions.first!
            storeDescription.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
            storeDescription.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
        }
        
        container.loadPersistentStores { _, error in
            if let error = error as NSError? {
                fatalError("CloudKit Core Data error: \(error), \(error.userInfo)")
            }
        }
        
        container.viewContext.automaticallyMergesChangesFromParent = true
        
        cloudKitManager = CloudKitManager(container: container)
        cloudKitManager.checkCloudKitStatus()
    }
}