原文宣布在我的博客wwww.fatbobman.com

欢迎订阅我的公共号:【肘子的Swift记事本】

Core Data 是一个具有数据耐久化才能的方针图结构。相同的方针图在不同的耐久化存储类型中( SQLite 、XML)的数据安排结构差别较大。假设你阅览过 Core Data 生成的 SQLite 数据库文件,必定会见过其间包含不少奇怪的表和字段。本文将对这些表和字段进行介绍,或许能够换个角度帮忙你解开部分疑问,例如: Core Data 为什么不需求主键、NSManagedObjectID 是怎么构成的 、保存冲突的判别依据是什么。

怎么获取 Core Data 的 SQLite 数据库文件

能够通过以下会集方法获取到 Core Data 生成的 SQLite 数据库文件:

  • 直接获取文件的存储地址

在代码中( 一般放置在 Core Data Stack 中,更多有关 Stack 的信息,请参阅 掌握 Core Data Stack )直接打印耐久化存储的保存方位,是最直接、高效的获取手法:

container.loadPersistentStores(completionHandler: { _, error in
    if let error = error as NSError? {
        fatalError("Unresolved error \(error), \(error.userInfo)")
    }
})
#if DEBUG
// 假设你有多个存储,且保存在不同的目录,需依次将其打印出来
if let url = container.persistentStoreCoordinator.persistentStores.first?.url {
    print(url)
}
#endif

Core Data 是怎么在 SQLite 中保存数据的

在 Finder 中通过快捷键( ⇧⌘ G )或菜单指令( 前往文件夹 )能够直接到达文件所在的方位。

Core Data 是怎么在 SQLite 中保存数据的

  • 启用调试参数

假设你在项目中敞开了 Core Data 的调试信息输出,那么能够直接在调试信息的顶部找到数据库的途径地址。

-com.apple.CoreData.CloudKitDebug 1

更多有关调试参数的内容,请参阅 Core Data with CloudKit(四)—— 调试、检验、搬家及其他

  • 通过断点查找

在运用实行进程中,通过任意断点暂停程序的实行,在调试窗口中输入如下指令,即可获得运用在沙盒中的根途径。

po NSHomeDirectory()
  • 第三方东西

一些第三方东西(例如 RocketSim)供应了直接访问模拟器中 App 目录的功用。

Core Data 是怎么在 SQLite 中保存数据的

读者最好能在打开一个由 Core Data 生成的 SQLite 数据库文件的情况下持续阅览接下来的内容

基础的表与字段

所谓基础的表与字段是指,在没有启用其他附加功用(耐久化前史跟踪、Core Data With CloudKit)的情况下,Core Data 为了满足根本功用而在 SQLite 数据库中创建的表( 非实体表 )和在实体表中创建的特别字段。

实体对应的表

下图为运用 Xcode Core Data 模板创建的项目的数据库结构(仅定义了一个实体 Item,且 Item 只有一个特色 timestamp ),其间实体 Item 在 SQLite 中对应的表是 ZITEM 。

Core Data 是怎么在 SQLite 中保存数据的

Core Data 依照如下规则将数据模型中的实体转换成 SQLite 的格式:

  • 实体对应的表名为 Z + 实体称谓(全部大写),本例中为 ZITEM

  • 实体中特色对应的字段为 Z + 特色称谓(全部大写),本例中为 ZTIMESTAMP

  • 对于大写后称谓一同的特色(特色在定义时是大小写活络的),将为其他重名特色添加编号。如 Item 有两个特色 timestamp 和 timeStamp ,将在表中创建两个字段 ZTIMESTAMP 及 ZTIMESTAMP1

  • 为每个实体表添加三个特别字段: Z_PK、Z_ENT、Z_OPT(均为 INTEGER 类型)

  • 如实体定义中包含联络,在实体表中为联络创建对应的字段或创建对应的中心联络表(具体内容见后文)

Z_ENT 字段

每个实体表均在 Z_PRIMARYKEY 表(下文胪陈)中进行了挂号。该字段与挂号记载的 Z_ENT 一同。能够将其视为表的 ID 。

Z_PK 字段

从 1 初步递加的整数,能够将其视为表的主键。Z_PK + Z_ENT ( 主键 + 表 ID )是 Core Data 在特定 SQLite 数据文件中查找具体条目的要害。

Z_OPT 字段

数据记载版别号。每一次对数据的批改,均会导致该值加一。

Z_PRIMARYKEY 表

Z_PRIMARYKEY 表是完成通过 Z_PK + Z_ENT 定位数据的基础。它的首要作用有:

  • 对 Core Data 在 SQLite 中创建的表(全部需求通过 Z_PK + Z_ENT 定位记载的表,不包含 Z_PRIMARYKEY、Z_METADATA、Z_MODELCACHE)进行挂号
  • 标明实体之间的联络(仅针对抽象实体)
  • 记载实体的称谓(数据模型中定义的称谓)
  • 记载每个挂号表其时已运用的最大 Z_PK 值

Z_ENT

表的 ID。实体表会从编号 1 初步,而为其他系统功用创建的表会从编号 16000 初步。下图展示了实体 Memo 表中的 Z_ENT 与 Memo 在 Z_PRIMARYKEY 表中记载的 Z_Ent 字段的对应联络。

Core Data 是怎么在 SQLite 中保存数据的

Core Data 是怎么在 SQLite 中保存数据的

Z_NAME 字段

实体在数据模型中的称谓(大小写活络),用于从 URL 反向查找对应数据( 具体运用见下文 )。

Z_SUPER 字段

假设实体为某个实体( Abstract Entity )的子实体,该值对应其父实体的 Z_ENT 。0 表明该实体没有父实体。下图展示了当 Item 为抽象实体,ItemSub 为它的子实体时 Z_SUPER 的情况。

Core Data 是怎么在 SQLite 中保存数据的

Core Data 是怎么在 SQLite 中保存数据的

Z_MAX 字段

标记了每个挂号表毕竟运用的 Z_PK 值。在创建新的实体数据时,Core Data 将从 Z_PRIMARYKEY 表中找到对应实体毕竟运用的 Z_PK 值( Z_MAX ),在此值基础上加一,作为新记载的 Z_PK 值,并更新该实体对应的 Z_MAX 值。

Z_METADATA 表

Z_METADATA 表中记载了与其时 SQLite 文件有关的信息,包含:版别、标识符以及其他元数据。

Z_UUID 字段

其时数据库文件的 ID 标识( UUID 类型)。能够通过保管方针协调器获取该值。在将 NSManagedObjectID 转换成可存储的 URL 时,该值表明对应的耐久化存储。

Z_PLIST 字段

选用 Plist 的格式存储的有关耐久化存储的元数据( 不包含耐久化存储的 UUID 标识 )。能够通过耐久化存储协调器来读取或添加数据。如有需求,开发者还能够在其间保存与数据库无关的数据( 能够将其视为通过 Core Data 的数据库文件保存程序配备的特别用法 )。

let coordinate = container.persistentStoreCoordinator
guard let store = coordinate.persistentStores.first else {
    fatalError()
}
var metadata = coordinate.metadata(for: store) // 获取元数据( Z_PLIST + Z_UUID )
metadata["Author"] = "fat" // 添加新的元数据
store.metadata = metadata
try! container.viewContext.save() // 除了在创建新的耐久化存储时添加 metadata 外,其他情况下添加的数据都需求显式调用上下文的 save 方法来结束耐久化

下图为将 Z_PLIST 中的数据( BLOB 格式 )导出成 Plist 格式后的情况:

Core Data 是怎么在 SQLite 中保存数据的

Z_VERSION 字段

具体作用不知道(估量为 Core Data 的 SQLite 格式版别),其时一直为 1 。

Z_MODELCACHE 表

尽管 Core Data 在 Z_METADATA 表中的 Z_PLIST 中保留了其时运用的数据模型版别的签名信息,但由于 Z_PLIST 的内容是可更改的,因此为了确保运用正在运用的数据模型版别与 SQLite 文件中的完全一同,Core Data 在 Z_MODELCACHE 表中保存了一份与其时 SQLite 数据对应的数据模型的缓存版别 (某种 mom 或 omo 的变体)。

Z_MODELCACHE 中的缓存数据和元数据中的数据模型签名一同为数据模型的版别验证和版别搬家供应了确保。

从数据库结构中得到的收成

在对 SQLite 的表和字段有了必定的了解后,一些困扰 Core Data 开发者的问题或许就会得到有用的解说。

为什么不需求主键

Core Data 通过实体表对应的 Z_MAX 主动为每条新增记载添加了自增主键数据。因此在 Core Data 定义数据模型时,开发者无须为实体特别定义主键特色(事实上也无法创建自增主键)。

NSManagedObjectID 的构成

保管方针的 NSManagedObjectID 由:数据库 ID + 表 ID + 实体表中的主键一同构成。在 SQLite 中对应的字段为 Z_UUID + Z_ENT + Z_PK 。通过将 NSManagedObjectID 转换成可存储格式的 URL ,能够将它的构成明晰地展示出来。

let url = itemSub.objectID.uriRepresentation()

Core Data 是怎么在 SQLite 中保存数据的

【 文件(耐久化存储)+ 表 + 行 】的信息组合也将帮忙 Core Data 完成从 URL 转换为对应的保管方针。

let url = URL(string:"x-coredata://E8B22CEA-8316-45E7-BC08-3FBA516F962C/ItemSub/p1")!
if let objectID = container.persistentStoreCoordinator.managedObjectID(forURIRepresentation: url) {
    if let itemSub = container.viewContext.object(with: objectID) as? ItemSub {
        ...
    } 
}

更多有关从 URL 转换成保管方针的内容请参阅 在 Spotlight 中展示运用中的 Core Data 数据。

怎么在数据库中标识联络

Core Data 利用了在同一个数据库中仅需依托 Z_ENT + Z_PK 即可定位记载的特性来完成了在不同的实体之间标明联络的作业。为了节约空间,Core Data 仅保存了每个联络记载的 Z_PK 数据,Z_ENT 则直接由数据模型从 Z_PRIMARYKEY 表中获取。

在数据库中创建联络的规则为:

  • 一对多

    “一”的一侧不创建新的字段,在“多”的一侧为联络创建新的字段,该字段对应“一”的 Z_PK 值。字段称谓为 Z + 联络称谓(大写)

  • 一对一

    联络两头都添加新的字段,分别为对应数据的 Z_PK 值

  • 多对一

    联络两头都不添加新的字段,创建一个表明该多对多联络的新表,并在其间逐行添加联络两头数据的 Z_PK 值。

    下图中,Item 与 Tag 为多对多联络,Core Data 创建了 Z_2TAGS 表来管理该联络数据。

    Core Data 是怎么在 SQLite 中保存数据的

在启用了抽象实体的情况下,除了记载对应联络数据的 Z_PK 值外,还会添加一个字段以记载该数据具体属于哪个 Z_ENT ( 父实体或某个子实体)。

保存冲突的判别

Core Data 在保存数据时,通过豁达锁的方法来判别是否会呈现保存冲突的情况。而豁达锁的判别依据则是依据每条记载的 Z_OPT 数据,选用了版别号机制。

在数据进行耐久化时,假设 Core Data 发现上下文的数据快照中的 Z_OPT 数据与行缓存中的纷歧同,或许行缓存中的 Z_OPT 与数据库文件纷歧同,均会认为是产生了保存冲突。

更多有关保存冲突的内容,请参阅 关于 Core Data 并发编程的几点提示 。

用于耐久化前史跟踪的表

在 CoreData 中,假设你的数据保存方法是 SQLite(绝大多数的开发者都选用此种方法)且启用了耐久化前史跟踪功用,无论数据库中的数据有了何种改动(删去、添加、批改等),调用此数据库并注册了该通知的运用,都会收到一个“数据库有改动”的系统提示。

近几年跟着 App Group、小组件、Core Data with CloudKit 、Core Data in Spotlight 等功用的运用,越来越多的 Core Data 运用中都主动或被动地敞开了耐久化前史跟踪选项。在启用了该功用后( desc.setOption(true as NSNumber,forKey: NSPersistentHistoryTrackingKey) ),Core Data 会在 SQLite 中新建三张表来管理和记载事务,并且会在 Z_PRIMARYKEY 表中挂号这三张表的信息。

更多具体的有关耐久化前史跟踪的内容,请参阅 在 CoreData 中运用耐久化前史跟踪 。

Core Data 是怎么在 SQLite 中保存数据的

Core Data 是怎么在 SQLite 中保存数据的

Z_ATRANSACTIONSTRING 表

为了能够分辩事务( Transaction )的来历,事务的产生者需求为保管方针上下文设置事务作者,Core Data 将全部的事务作者的信息都汇总在 Z_ATRANSACTIONSTRING 表中。

container.viewContext.transactionAuthor = "fatbobman"

假设开发者也为上下文也设置了称谓,那么 Core Data 也将为该上下文称谓创建一条记载

container.viewContext.name = "viewContext"

Core Data 是怎么在 SQLite 中保存数据的

Core Data 还会为一些其他的系统功用创建默许的作者记载。在处理事务时,应疏忽这些系统作者产生的事务。

Z_PK 和 Z_ENT 的含义与上文中一同,后文将不再赘述

Z_ATRANSACTION 表

你能够将耐久化前史跟踪的事务了解为在 Core Data 中的某一次耐久化进程(比如调用上下文的 save 方法)。Core Data 将与某次事务有关的信息保存在 Z_ATRANSACTION 表中。其间最为要害的信息是事务创建的时间和事务作者。

Core Data 是怎么在 SQLite 中保存数据的

ZAUTHORTS 字段

对应 Z_ATRANSACTIONSTRING 表中的事务作者的 Z_PK 。上图中对应的是 Z_ATRANSACTIONSTRING 中的 Z_PK 为 1 的 fatbobman 。

ZCONTEXTNAMETS 字段

假设为创建事务的上下文设置了称谓,则该字段对应上下文称谓在 Z_ATRANSACTIONSTRING 表中的记载的 Z_PK 。上图对应的是 viewContext 。

ZTIMESTAMP 字段

事务的创建时间。

ZQUERYGEN 字段

假设为保管方针上下文设置了确认查询令牌( NSQueryGenerationToken ),那么事务记载中还会将其时的查询令牌保存在 ZQUERYGEN 字段中 ( BLOB 类型 )。

try? container.viewContext.setQueryGenerationFrom(.current)

Z_ACHANGE 表

在一次事务中,一般会包含若干个数据操作(创建、更改、删去)。Core Data 将每个数据操作都保持在 Z_CHANGE 表中,并通过 Z_PK 与特定的事务进行相关。

Core Data 是怎么在 SQLite 中保存数据的

ZCHANGETYPE 字段

数据操作类型:0 新建 1 更新 2 删去

ZENTITY 字段

操作对应的实体表的 Z_ENT

ZENTITYPK 字段

操作对应的数据记载在实体表中的 Z_PK

ZTRANSACTIONID 字段

操作对应的事务在 Z_ATRANSACTION 表中的 Z_PK

从 SQLite 角度知道耐久化前史跟踪

创建事务

在耐久化前史跟踪中,创建事务的作业是由 Core Data 主动结束的,大概的流程如下:

  • 从 Z_PRIMARYKEY 表中获取 Z_ATRANSACTION 的 Z_MAX
  • 运用 Z_PK ( Z_MAX + 1 ) + Z_ENT ( 事务表在 Z_PRIMARYKEY 中对应的 Z_ENT ) + 作者 ID + 时间戳 在 Z_ATRANSACTION 中创建新事务记载,并更新 Z_MAX
  • 获取 Z_ACHANGE 的 Z_MAX
  • 在 Z_ACHANGE 中逐条创建数据操作记载

查询事务

因为数据库中只保存了事务创建的时间戳,因此无论选用哪种查询方法(时间 Date、令牌 NSPersistentHistoryToken、事务 NSPersistentHistoryTransaction )毕竟都会转换成比较时间戳的方法。

  • 时间戳晚于前次其时运用的查询时间
  • 作者不是其时 App 的作者或其他系统功用作者
  • 获取满足上述条件的全部 Z_CHANGE 记载

合并事务

事务中提取的数据操作记载( Z_ACHANGE )中包含了完好的操作类型、对应的实例数据方位等信息,按图索骥从数据库中提取实体数据( Z_PK + Z_ENT )并将其合并( 转换成 NSManagedObjectID )到指定的上下文中。

删去事务

  • 查询并提取时间戳早于全部作者( 包含其时运用作者,但不包含系统功用作者 )的毕竟查询时间的事务
  • 删去上述事务( Z_ATRANSACTION )及其对应的操作数据( Z_ACHANGE )。

了解上述进程对了解 Persistent History Tracking Kit 的代码很有帮忙

其他

假设你的运用运用了 Core Data with CloudKit ,那么在阅览 SQLite 数据结构时你将获得进一步的惊喜()。Core Data 将创建更多的表来处理与 CloudKit 的同步事宜。考虑到表的复杂性和篇幅,就不持续展开了。不过有了上文的基础,了解它们的用途也并非很困难。

下图为敞开了私有数据库同步功用后 SQLite 中新增的系统表:

Core Data 是怎么在 SQLite 中保存数据的

这些表首要记载了:CloudKit 私有域信息、前次同步时间、前次同步令牌、导出操作日志、导入操作日志、待导出数据、Core Data 联络与 CloudKit 联络对照表、本地数据对应的 CKRecordName、本地数据的 CKRecord 完好镜像( 同享公共数据库 )等等信息。

跟着 Core Data 功用的不断添加,将来可能会看到更多的系统功用表。

总结

撰写本文的首要目的是对我近段时间来的零星研讨进行汇总,方便日后查询。因此即便你已经完全掌握了 Core Data 的外部存储结构,但最好还是尽量不要直接对数据库进行操作,苹果可能在任何时间改动它的底层完成。

期望本文能够对你有所帮忙。

原文宣布在我的博客wwww.fatbobman.com

欢迎订阅我的公共号:【肘子的Swift记事本】