英文:
Saving Core Data to external backup file
问题
我正在开发一个旅行规划应用程序,使用Core Data进行数据存储。我的多个实体中有两个(Person和Trip)具有多对多的关系(一个人可以参加多个旅行,而一个旅行可以包括多个人)。我想要添加一个功能,让用户有机会创建一个外部备份文件,将其数据保存为txt文件,可以存储在iCloud中或Files中的某个位置。我需要能够进行序列化和反序列化以进行备份和恢复操作。
根据我所了解的情况,JSON似乎只能处理一对多的关系(严格的分层关系),而不能处理多对多。如果我理解错了,它确实可以处理多对多关系,是否有人可以帮助我理解如何进行序列化?
但如果实际上不能处理多对多关系,那么还有哪些其他方法可以推荐用于将Core Data备份到txt文件中?理想情况下,这应该是Swift具有本机方法用于序列化和反序列化的内容。
英文:
I have a travel planner app I’m working on that uses Core Data for data storage. Two of my many entities (Person and Trip) have a many-to-many relationship (one person may go on many trips, and a single trip includes many people). I would like to add a feature that gives the user the opportunity to create an external backup file of their data as a txt file that can either live in iCloud, or a location in Files. I will need to be able to serialize and deserialize for backup and restore operations.
From what I’ve read it seems that JSON can only do one-to-many relationships (strictly hierarchical), not many-to-many. If I’m mistaken and it can indeed do many-to-many, can someone help me understand how to serialize that?
But if in fact it cannot, what other methods would be recommended for backing up Core Data into a txt file? Ideally this would be something that Swift has native methods for serializing and deserializing.
答案1
得分: 1
以下是您要翻译的内容:
class Person: NSManagedObject {
@NSManaged var id: UUID
@NSManaged var trips: Set<Trip>
}
class Trip: NSManagedObject {
@NSManaged var id: UUID
@NSManaged var persons: Set<Person>
}
一种分享这种关系(到第三方库、服务器或者在您的情况下是复制)的方法是将“多对多”关系转换为UUID数组。这样,其他方只需在需要时“重新建立”连接。
如果我们考虑JSON(但也可以是XML或任何其他格式),一种方法是在“一大文件模式”下:
{
"persons": [
{ "id": "personId1", "trips": ["tripId1", "tripId2", "tripId3"] },
{ "id": "personId2", "trips": ["tripId3", "tripId5"] }
],
"trips": [
{ "id": "tripId1", "persons": ["personId1"] },
{ "id": "tripId2", "persons": ["personId1"] },
{ "id": "tripId3", "persons": ["personId1", "personId2"] },
{ "id": "tripId4", "persons": [] },
{ "id": "tripId5", "persons": ["personId2"] }
]
}
您现在可以创建一个导出方法:
extension Person {
func export() -> [String: Any] {
["id": id.uuidString, "trips": trips.map { $0.id.uuidString }]
}
}
extension Trip {
func export() -> [String: Any] {
["id": id.uuidString, "persons": persons.map { $0.id.uuidString }]
}
}
您还可以遍历每个属性,以获得更“通用”的东西:
extension Person {
func exportAttributes() -> [String: Any] {
entity.attributesByName.reduce(into: [String: Any]()) { $0[$1.key] = value(forKey: $1.key) }
}
// 带有额外工作的属性导出示例
func exportAttributesWithExtraWork() -> [String: Any] {
entity.attributesByName.reduce(into: [String: Any]()) {
var finalKey = $1.key
switch finalKey {
case #keyPath(Person.id):
finalKey = "personId" // 在这里,我们想要另一个键
default:
break
}
switch $1.value.attributeType {
case .dateAttributeType:
guard let date = value(forKey: $1.key) as? Date else { return }
$0[finalKey] = date.timeIntervalSince1970 // 这只是一个示例,根据其类型更改值
default:
$0[finalKey] = value(forKey: $1.key)
}
}
}
// 关系的示例
func exportRelations() -> [String: Any] {
entity.relationshipsByName.reduce(into: [String: Any]()) {
if let trips = value(forKey: $1.key) as? Set<Trip> {
$0[$1.key] = trips.map { aTrip in aTrip.id.uuidString }
}
}
}
func fullExport() -> [String: Any] {
var dictionary: [String: Any] = [:]
let attributes = exportAttributes()
dictionary.merge(attributes, uniquingKeysWith: { (_, new) in return new })
let relations = exportRelations()
dictionary.merge(relations, uniquingKeysWith: { (_, new) in return new })
return dictionary
}
}
英文:
So, you have something like that:
class Person: NSManagedObject {
@NSManaged var id: UUID
@NSManaged var trips: Set<Trip>
}
class Trip: NSManagedObject {
@NSManaged var id: UUID
@NSManaged var persons: Set<Person>
}
One way to share that (to a third party lib, a server, or in your case a copy), could be to transforms the "many-to-many" into arrays of UUIDs. That way, the other party just have to "redo" the connection if needed.
If we think in JSON (but it could be XML, or any other format), one way would be, in "one big file mode"
{
"persons": [
{ "id": "personId1", "trips": ["tripId1",
"tripId2",
"tripId3"]
},
{ "id": "personId2", "trips": ["tripId3",
"tripId5"]
},
],
"trips": [
{"id": "tripId1", "persons": ["personId1"]},
{"id": "tripId2", "persons": ["personId1"]},
{"id": "tripId3", "persons": ["personId1", "personId2"]},
{"id": "tripId4", "persons": []},
{"id": "tripId5", "persons": ["personId2"]},
]
}
You can now create an export method:
extension Person {
func export() -> [String: Any] {
["id": id.uuidString,
"trips": trips.map { $0.id.uuidString }]
}
}
extension Trip {
func export() -> [String: Any] {
["id": id.uuidString,
"persons": persons.map { $0.id.uuidString }]
}
}
You could also iterate on each properties to have something more "generic":
extension Person {
func exportAttributes() -> [String: Any] {
//might need a `context.perform()` here, it depends who calls it, from where, could be async method, or a with a completion, it's up to your own code
entity.attributesByName.reduce(into: [String: Any]()) { $0[$1.key] = value(forKey: $1.key) }
}
//Sample with computing some values
func exportAttributesWithExtraWork() -> [String: Any] {
entity.attributesByName.reduce(into: [String: Any]()) {
var finalKey = $1.key
switch finalKey {
case #keyPath(Person.id):
finalKey = "personId" //Here, we want to have another key
default:
break
}
switch $1.value.attributeType {
case .dateAttributeType:
guard let date = value(forKey: $1.key) as? Date else { return }
$0[finalKey] = date.timeIntervalSince1970 //It's just to show a sample where you change the value according to its type
default:
$0[finalKey] = value(forKey: $1.key)
}
}
}
//Sample for relations
func exportRelations() -> [String: Any] {
entity.relationshipsByName.reduce(into: [String: Any]()) {
if let trips = value(forKey: $1.key) as? Set<Trips> {
$0[$1.key] = trips.map { aTrip in aTrip.id.uuidString }
}
}
}
func fullExport() -> [String: Any] {
var dictionary: [String: Any] = [:]
let attributes = exportAttributes()
dictionary.merge(attributes, uniquingKeysWith: { (_, new) in return new })
let relations = exportRelations()
dictionary.merge(relations, uniquingKeysWith: { (_, new) in return new })
return dictionary
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论