Core Data
Core Dataは、Appleが提供するオブジェクトグラフ管理と永続化を行うフレームワークです。iOSやmacOS開発において長年の実績を誇り、データを直接操作するのではなく、オブジェクトとして扱うことで複雑なデータ管理を抽象化します。NSManagedObjectContextを中心とした強力なメモリ管理、リレーションシップ処理、遅延読み込み、自動的な変更追跡機能を提供し、企業レベルのアプリケーション開発に必要な信頼性とパフォーマンスを実現しています。
GitHub概要
swiftlang/swift-corelibs-foundation
The Foundation Project, providing core utilities, internationalization, and OS independence
トピックス
スター履歴
ライブラリ
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()
}
}