iOS Core Data 本地數據庫 使用詳解:從模型關系到數據操作

一、引言:Core Data,在本地數據持久化中的地位

在 iOS 開發中,本地數據存儲幾乎是每一個 App 都繞不開的問題。無論是緩存用戶信息、離線瀏覽內容,還是記錄用戶操作歷史,一個合適的數據持久化方案都能大大提升應用的體驗和性能。

而說到蘋果官方提供的解決方案,Core Data?無疑是最具代表性的工具之一。它不僅僅是一個數據庫封裝工具,更是一個面向對象的數據模型框架,允許我們以結構化方式描述實體之間的關系,并通過上下文(Context)來完成對象的創建、查詢、更新和刪除。

很多人可能對 Core Data 保持一定的距離,覺得它“上手復雜”“冗余配置多”,但其實,Core Data 在近年來的演進中已經發生了非常大的變化:

  • NSPersistentContainer?的引入,極大簡化了配置步驟;
  • Xcode 支持自動生成模型類,無需手動維護 NSManagedObject 子類;
  • 與 SwiftUI 的集成也越來越順暢(雖然本文暫不涉及);

在這篇文章中,我們將以兩個簡單的實體模型為例:PHDramaEntity?和?PHEpisodeEntity,構建一個“一對多”的關系結構,完整演示 Core Data 從模型創建到數據操作的基本用法。

無論你是第一次接觸 Core Data,還是想重新認識這個框架,相信這篇文章都能帶你快速上手,并掌握它的核心用法。

二、創建 Core Data 模型文件與實體類

在使用 Core Data 之前,我們第一步需要做的,就是創建一個數據模型文件,并在其中定義好我們項目所需的實體(Entity)、屬性(Attribute)和關系(Relationship)。

🛠? 新建?.xcdatamodeld?文件

首先,我們在項目中新增一個 Core Data 模型文件:文件名:AmericanDramaDB。你可以起任何名字,也可以直接使用默認的名稱。

你可以通過 Xcode 的「File → New → File… → Data Model」選項來添加這個模型文件。

📦 創建?PHDramaEntity?實體

接下來,我們在模型中創建一個名為?PHDramaEntity?的實體,它表示一部劇,包含劇名、封面、分組信息等字段。同時,它還會關聯多個劇集。

實體字段設計如下:

  1. id: Int64
  2. title: String?
  3. coverFileName: String?
  4. packageID: String?
  5. type: Int64
  6. index: Int64
  7. episodeList: To-Many 關系(指向 PHEpisodeEntity)

其中?episodeList?是一對多關系,表示該劇所擁有的所有劇集。Core Data 支持我們在模型中直接配置這種實體間的引用關系。

Xcode 會根據模型自動生成如下代碼:

extension PHDramaEntity {@nonobjc public class func fetchRequest() -> NSFetchRequest<PHDramaEntity> {return NSFetchRequest<PHDramaEntity>(entityName: "PHDramaEntity")}@NSManaged public var coverFileName: String?@NSManaged public var id: Int64@NSManaged public var index: Int64@NSManaged public var packageID: String?@NSManaged public var title: String?@NSManaged public var type: Int64@NSManaged public var episodeList: NSSet?
}// MARK: - Generated accessors for episodeList
extension PHDramaEntity {@objc(addEpisodeListObject:)@NSManaged public func addToEpisodeList(_ value: PHEpisodeEntity)@objc(removeEpisodeListObject:)@NSManaged public func removeFromEpisodeList(_ value: PHEpisodeEntity)@objc(addEpisodeList:)@NSManaged public func addToEpisodeList(_ values: NSSet)@objc(removeEpisodeList:)@NSManaged public func removeFromEpisodeList(_ values: NSSet)
}extension PHDramaEntity : Identifiable { }

你到不需要找到代碼在哪,編譯成功之后就可以直接使用這個實體及其相關的屬性和方法。

🎞? 創建?PHEpisodeEntity?實體

接下來是?PHEpisodeEntity,它代表具體的某一集劇集,字段更豐富一些,還關聯了卡片、閱讀記錄等內容。

字段設計如下:

  1. id: Int64
  2. title: String?
  3. index: Int64(集數順序)
  4. episodeCoverFileName: String?
  5. packageId: String?
  6. dramaId: Int64
  7. progress: Double
  8. readDate: Date?
  9. type: Int64
  10. cardList: To-Many(指向 PHCardEntity)
  11. readDateList: To-Many(指向 PHReadRecordEntity)
  12. drama: To-One(指向 PHDramaEntity,作為反向引用)

我們可以只考慮PHEpisodeEntity和PHDramaEntity兩個實體,其它實體和關系暫且不需要考慮,不影響對Core Data使用的理解。

Xcode 同樣生成如下代碼:

extension PHEpisodeEntity {@nonobjc public class func fetchRequest() -> NSFetchRequest<PHEpisodeEntity> {return NSFetchRequest<PHEpisodeEntity>(entityName: "PHEpisodeEntity")}@NSManaged public var dramaId: Int64@NSManaged public var episodeCoverFileName: String?@NSManaged public var id: Int64@NSManaged public var index: Int64@NSManaged public var packageId: String?@NSManaged public var progress: Double@NSManaged public var readDate: Date?@NSManaged public var title: String?@NSManaged public var type: Int64@NSManaged public var cardList: NSSet?@NSManaged public var drama: PHDramaEntity?@NSManaged public var readDateList: NSSet?
}// MARK: - Generated accessors for cardList
extension PHEpisodeEntity {@objc(addCardListObject:)@NSManaged public func addToCardList(_ value: PHCardEntity)@objc(removeCardListObject:)@NSManaged public func removeFromCardList(_ value: PHCardEntity)@objc(addCardList:)@NSManaged public func addToCardList(_ values: NSSet)@objc(removeCardList:)@NSManaged public func removeFromCardList(_ values: NSSet)
}// MARK: - Generated accessors for readDateList
extension PHEpisodeEntity {@objc(addReadDateListObject:)@NSManaged public func addToReadDateList(_ value: PHReadRecordEntity)@objc(removeReadDateListObject:)@NSManaged public func removeFromReadDateList(_ value: PHReadRecordEntity)@objc(addReadDateList:)@NSManaged public func addToReadDateList(_ values: NSSet)@objc(removeReadDateList:)@NSManaged public func removeFromReadDateList(_ values: NSSet)
}extension PHEpisodeEntity : Identifiable { }

值得一提的是,drama?是一個?反向關系,它使我們可以在訪問劇集時,直接找到它所屬的劇,方便非常多。

以上就是使用 Core Data 創建數據模型的全過程。

下一步,我們將配置 Core Data 棧,準備好?NSPersistentContainer?和上下文,真正開始使用 Core Data 的增刪改查功能。

三、配置 Core Data 棧:管理上下文與持久容器

完成模型文件的創建后,我們就可以正式初始化 Core Data 的持久化棧了。這一步的核心就是構建?NSPersistentContainer,并拿到?NSManagedObjectContext,用于后續的數據操作。

為此,我們可以創建一個專門的 Core Data 管理類,比如命名為?PHCoreDataManager,采用單例模式進行統一管理。

🧱 創建 Core Data 管理類

import CoreDataclass PHCoreDataManager: NSObject {static let shared = PHCoreDataManager()let container: NSPersistentContainervar context: NSManagedObjectContext {container.viewContext}private override init() {container = NSPersistentContainer(name: "AmericanDramaDB")container.loadPersistentStores { description, error inif let error = error {fatalError("Core Data 加載失敗: \(error)")}}}/// 保存上下文func saveContext() {do {try context.save()} catch {print("保存失敗: \(error)")}}
}

📌 關鍵解釋

  • NSPersistentContainer(name:)?中的參數要與你的?.xcdatamodeld?文件名一致(不含擴展名),否則會找不到模型。
  • loadPersistentStores?是異步加載持久化存儲的過程,建議在其中加上錯誤處理。
  • container.viewContext?是我們最常使用的上下文,用于主線程讀寫操作。
  • saveContext()?方法建議封裝在這里,方便統一調用,避免遺漏保存。

??注意:Core Data 的操作都基于 context,創建、修改、刪除對象后都需要調用?save()?才會真正落盤。如果不保存,應用重啟后數據會丟失。

四、增刪改查:以 PHDramaEntity 為例

有了模型和 Core Data 棧之后,我們終于可以開始使用 Core Data 進行數據操作了。下面我們就以?PHDramaEntity為例,演示最常見的增、刪、改、查操作。

1?? 插入數據(Create)

我們先演示如何創建一條新的?PHDramaEntity?數據,并保存到數據庫中。

let context = PHCoreDataManager.shared.contextlet drama = PHDramaEntity(context: context)
drama.id = 1001
drama.title = "絕命毒師"
drama.coverFileName = "breaking_bad.jpg"
drama.packageID = "breaking-bad"
drama.type = 1
drama.index = 0PHCoreDataManager.shared.saveContext()

每次創建實體對象時,都需要傳入?context,這是 Core Data 的核心機制。

2?? 查詢數據(Read)

通過?NSFetchRequest?可以查詢所有?PHDramaEntity?數據,按標題排序:

let request: NSFetchRequest<PHDramaEntity> = PHDramaEntity.fetchRequest()
request.sortDescriptors = [NSSortDescriptor(key: "title", ascending: true)]do {let dramas = try context.fetch(request)for drama in dramas {print("劇名:\(drama.title ?? "未知"),封面:\(drama.coverFileName ?? "無")")}
} catch {print("查詢失敗:\(error)")
}

你可以通過?NSPredicate?添加條件過濾,比如查找指定 packageID 的劇。

3?? 更新數據(Update)

我們以“更新某個指定 ID 的劇的標題”為例:

let request: NSFetchRequest<PHDramaEntity> = PHDramaEntity.fetchRequest()
request.predicate = NSPredicate(format: "id == %d", 1001)do {if let drama = try context.fetch(request).first {drama.title = "絕命毒師(更新后)"PHCoreDataManager.shared.saveContext()print("更新成功")}
} catch {print("更新失敗:\(error)")
}

只需要修改對象屬性后再?saveContext()?即可完成更新。

4?? 刪除數據(Delete)

刪除也是非常直接,只需要調用?context.delete(_:):

let request: NSFetchRequest<PHDramaEntity> = PHDramaEntity.fetchRequest()
request.predicate = NSPredicate(format: "id == %d", 1001)do {if let drama = try context.fetch(request).first {context.delete(drama)PHCoreDataManager.shared.saveContext()print("刪除成功")}
} catch {print("刪除失敗:\(error)")
}

刪除對象后,也一定記得調用?saveContext(),否則不會真正從數據庫移除。

五、操作一對多關系:從劇到劇集的增查操作

在前面我們已經定義好了?PHDramaEntity?和?PHEpisodeEntity?的一對多關系:一個劇(Drama)包含多個劇集(Episode),我們通過?episodeList?來描述這種引用。

本節將演示兩個核心操作:

  • 如何向?PHDramaEntity?添加多個?PHEpisodeEntity
  • 如何從一個劇中讀取它的所有劇集

1?? 添加多個劇集到某個劇

假設我們已經有一個?PHDramaEntity?對象,接下來我們要為它添加兩集內容。

let context = PHCoreDataManager.shared.context// 創建劇
let drama = PHDramaEntity(context: context)
drama.id = 2001
drama.title = "紙牌屋"
drama.packageID = "house-of-cards"
drama.index = 1
drama.type = 1// 創建劇集 1
let ep1 = PHEpisodeEntity(context: context)
ep1.id = 1
ep1.title = "第一集"
ep1.index = 1
ep1.drama = drama // 反向關聯// 創建劇集 2
let ep2 = PHEpisodeEntity(context: context)
ep2.id = 2
ep2.title = "第二集"
ep2.index = 2
ep2.drama = drama // 同樣反向關聯// 保存
PHCoreDataManager.shared.saveContext()

??推薦做法:通過設置劇集的 drama 屬性來建立反向引用關系,Core Data 會自動同步 episodeList。

當然你也可以反向添加:

drama.addToEpisodeList(ep1)
drama.addToEpisodeList(ep2)

兩種方式等效,哪種更符合你的使用習慣都可以。

2?? 從劇中讀取所有劇集

Core Data 一對多關系的字段類型通常是?NSSet?,所以我們需要進行類型轉換。

if let episodeSet = drama.episodeList as? Set<PHEpisodeEntity> {let sortedEpisodes = episodeSet.sorted { $0.index < $1.index }for episode in sortedEpisodes {print("劇集 \(episode.index):\(episode.title ?? "未知")")}
}

為了更方便使用,你也可以在?PHDramaEntity?中擴展一個 computed property:

extension PHDramaEntity {var sortedEpisodeArray: [PHEpisodeEntity] {let set = episodeList as? Set<PHEpisodeEntity> ?? []return set.sorted { $0.index < $1.index }}
}

這樣你就可以直接這樣用:

for episode in drama.sortedEpisodeArray {print(episode.title ?? "")
}

六、結語:Core Data,其實沒你想的那么復雜

在這篇文章中,我們從零開始,一步步搭建了 Core Data 的使用框架:

  • 創建數據模型文件,并定義實體和一對多關系;
  • 初始化 Core Data 棧,封裝?NSPersistentContainer;
  • 演示了如何對實體進行增刪改查操作;
  • 展示了一對多關系的建立與遍歷方式;

你可以看到,Core Data 的使用并沒有傳說中那么復雜。隨著?NSPersistentContainer?的出現,以及 Xcode 對模型類的自動生成支持,開發者已經可以非常高效地在項目中集成本地數據持久化功能。

當然,本文只是 Core Data 的起點。后續你還可以探索:

  • 如何設置刪除規則(Cascade、Nullify 等);
  • 如何使用?NSFetchedResultsController?優雅地驅動 UI;
  • 如何與 SwiftUI 結合,使用?@FetchRequest?實時監聽數據變化;
  • 如何進行數據遷移(Model Versioning);

但只要你掌握了本文的內容,Core Data 的世界就已經向你敞開大門。

如果你還沒在項目中使用過 Core Data,不妨就從本文的例子開始,動手試一試吧 🙂

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/92630.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/92630.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/92630.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Java-79 深入淺出 RPC Dubbo 動態路由架構詳解:從規則設計到上線系統集成

點一下關注吧&#xff01;&#xff01;&#xff01;非常感謝&#xff01;&#xff01;持續更新&#xff01;&#xff01;&#xff01; &#x1f680; AI篇持續更新中&#xff01;&#xff08;長期更新&#xff09; AI煉丹日志-30-新發布【1T 萬億】參數量大模型&#xff01;Kim…

Linux內核中動態內存分配函數解析

在C語言中&#xff0c;動態內存分配通常用于在運行時申請內存。在內核編程中&#xff0c;動態內存分配與用戶空間有所不同&#xff0c;因為內核需要更謹慎地處理內存&#xff0c;且不能使用用戶空間的庫&#xff08;如glibc&#xff09;。下面我們將詳細分析Linux內核中動態申請…

Next.js 中配置不同頁面布局方案

在 Next.js 應用中&#xff0c;你可以通過多種方式實現某些頁面全屏、某些頁面帶菜單/頁眉/頁腳的需求。以下是幾種實現方案&#xff1a; 方案一&#xff1a;使用多個布局組件 1. 創建不同的布局組件 // app/default-layout.tsx import Header from /components/header; import…

Spring Boot 使用外置 Servlet 容器:從配置到部署全指南

在 Spring Boot 開發中&#xff0c;我們通常使用嵌入式 Servlet 容器&#xff08;如 Tomcat&#xff09;&#xff0c;它能將應用打包成可執行 JAR&#xff0c;簡化部署流程。但在某些場景下&#xff08;如需要支持 JSP、復雜的容器定制或企業級部署規范&#xff09;&#xff0c…

借助AI學習開源代碼git0.7之九diff-files

借助AI學習開源代碼git0.7之九diff-files diff-files.c 是一個用于比較工作目錄中的文件和 Git 索引&#xff08;暫存區&#xff09;中文件的工具。 實質上&#xff0c;它是 git diff命令在不指定特定提交時功能的核心實現。 主要功能分析&#xff1a; 1. 核心功能 diff-files …

社區資源媒體管理系統設計與實現

社區資源媒體管理系統設計與實現 1. 系統概述 社區資源媒體管理系統是一個專為社區戶外廣告打造的高效、專業化平臺&#xff0c;旨在實現社區媒體的數字化管理、智能投放和便捷交易。該系統將整合社區各類廣告資源&#xff0c;為廣告主、物業公司和社區居民提供一站式服務。 1.…

12.1.6 weak_ptr

weak_ptr weak_ptr會指向一個share_ptr&#xff08;使用一個share_ptr來初始化weak_ptr&#xff09;&#xff0c;但并不會增加這個share_ptr的引用計數器&#xff0c;其析構也不會減少share_ptr的引用計數器。 構造函數及使用 #include <iostream> #include <memory&g…

深度分析Java內存模型

Java 內存模型&#xff08;Java Memory Model, JMM&#xff09;是 Java 并發編程的核心基石&#xff0c;它定義了多線程環境下線程如何與主內存&#xff08;Main Memory&#xff09;以及線程的本地內存&#xff08;工作內存&#xff0c;Working Memory&#xff09;交互的規則。…

代碼隨想錄算法訓練營第五十二天|圖論part3

101. 孤島的總面積 題目鏈接&#xff1a;101. 孤島的總面積 文章講解&#xff1a;代碼隨想錄 思路&#xff1a; 與島嶼面積差不多&#xff0c;區別是再dfs的時候&#xff0c;如果碰到越界的&#xff0c;需要用一個符號標記這不是孤島再continue #include <iostream> #i…

前端實現 excel 數據導出,封裝方法支持一次導出多個Sheet

一、前言 后臺管理項目有時會有需要前端導出excel表格的功能&#xff0c;有時還需要導出多個sheet&#xff0c;并給每個sheet重新命名&#xff0c;下面我們就來實現一下。 二、實現效果圖 三、實現步驟 1、 安裝 命令行安裝 xlsx 和 file-saver npm install xlsx -S npm i…

【Lambda 表達式】返回值為什么是auto

一個例子&#xff1a; int x 10; auto add_x [x](int y) -> int {return x y; }; int result add_x(5); // 結果是 15lambda 是匿名類型&#xff0c;必須用 auto 來接收。&#xff08;必須寫auto&#xff0c;不可省略&#xff09;內層 -> auto 是函數的返回類型自動推…

【小董談前端】【樣式】 CSS與樣式庫:從實現工具到設計思維的跨越

CSS與樣式庫&#xff1a;從實現工具到設計思維的跨越 一、CSS的本質&#xff1a;樣式實現的「施工隊」 CSS作為網頁樣式的描述語言&#xff0c;其核心能力在于&#xff1a; 精確控制元素的尺寸、位置、顏色實現響應式布局和動畫效果與HTML/JavaScript協同完成交互體驗 但CS…

MTSC2025參會感悟:大模型 + CV 重構全終端 UI 檢測技術體系

目錄 一、傳統 UI 自動化的困局:高成本與低效率的雙重枷鎖 1.1 根深蒂固的技術痛點 1.2 多維度質量挑戰的疊加 二、Page eyes 1.0:純視覺方案破解 UI 檢測困局 2.1 純視覺檢測的核心理念 2.2 頁面加載完成的智能判斷 2.3 視覺模型驅動的異常檢測 2.4 大模型賦能未知異…

使用Claude Code從零到一打造一個現代化的GitHub Star項目管理器

在日常的開發工作中&#xff0c;我們經常會在GitHub上star一些有用的項目庫。隨著時間的推移&#xff0c;star的項目越來越多&#xff0c;如何有效管理這些項目成為了一個痛點。 今天&#xff0c;分享我使用Claude Code從零構建的一個GitHub Star管理插件。項目背景與需求分析 …

為什么 Linux 啟動后還能升級內核?

? 為什么 Linux 啟動后還能升級內核&#xff1f; 簡單結論&#xff1a; 因為 “安裝/升級內核 ≠ 當前就使用該內核”&#xff0c;Linux允許你安裝多個內核版本&#xff0c;并在下次啟動時選擇其中一個來加載運行。 &#x1f9e0; 舉個現實生活類比 你在穿一件衣服&#xff08…

Go語言實戰案例-統計文件中每個字母出現頻率

以下是《Go語言100個實戰案例》中的 文件與IO操作篇 - 案例19&#xff1a;統計文件中每個字母出現頻率 的完整內容。本案例適合用來練習文件讀取、字符處理、map統計等基礎技能。&#x1f3af; 案例目標讀取一個本地文本文件&#xff0c;統計并打印出其中每個英文字母&#xff…

Notepad++工具操作技巧

1、notepad -> ctrlf -> 替換(正則表達式) -> $-a ->每行的行尾加a&#xff1b; 2、notepad -> ctrlf -> 替換(正則表達式) -> ^-a ->每行的行首加a &#xff1b; 3、按住alt切換為列模式 4、刪除空行-不包括有空格符號的空行 查找替代 查找目標…

領碼課堂 | Java與AI的“硬核“交響曲:當企業級工程思維遇上智能時代

摘要 &#x1f680; 在AI工業化落地的深水區&#xff0c;Java正以其獨特的工程化優勢成為中流砥柱。本文系統解構Java在AI項目全生命周期中的技術矩陣&#xff0c;通過"三階性能優化模型"、"微服務化AI部署架構"等原創方法論&#xff0c;結合大模型部署、M…

面經 - 基于Linux的高性能在線OJ平臺

真實面試環境中&#xff0c;被問到的相關問題&#xff0c;感興趣的可以看下1. 這個項目是你獨立完成的嗎&#xff1f;團隊中你的職責是什么&#xff1f;是的&#xff0c;這個項目是我獨立完成的&#xff0c;從需求分析、系統設計到項目部署都我做的。重點工作包括&#xff1a;使…

Ubuntu 20.04 上安裝 SPDK

以下是在 Ubuntu 20.04 上安裝 SPDK (Storage Performance Development Kit) 的完整步驟&#xff1a;1. 系統準備# 更新系統 sudo apt update sudo apt upgrade -y# 安裝基礎依賴 sudo apt install -y git make gcc g libssl-dev libaio-dev libnuma-dev \pkg-config python3 p…