如何允许 CoreData 实体列表中的重复条目
How to allow duplicate entries in a list of CoreData entities
在 CoreData 中,NSManagedObject
的每个实例都是唯一的。这就是为什么 CoreData 使用 NSSet
(及其对应的有序对象 NSOrderedSet
)来表示集合。但是,我需要一个允许一个项目 多次出现 .
的列表
我的直觉是将每个对象包装在一个 ListItem
实体中,并使用 NSOrderedSet
生成列表。由于列表项本身是唯一的,因此对象可以根据需要在列表中出现多次。然而,这会产生意想不到的结果。
示例应用程序
在此示例应用程序 iFitnessRoutine 中,用户可以 select 从活动列表中选择,例如开合跳、仰卧起坐和弓步。然后他们可以构造一个 FitnessCircuit
来创建一个活动列表并在一定时间内执行每个活动。例如:
上午巡回赛:
- 开合跳:60 秒
- 弓步:60 秒
- 仰卧起坐:60 秒
- 开合跳:60 秒
- 仰卧起坐:60 秒
- 开合跳:60 秒
在我的实现中,每个 Activity
都包含在一个 ListItem
中,但是结果会产生如下内容:
上午巡回赛:
- ListItem -> 开合跳:60 秒
- ListItem -> 无
- ListItem -> 弓步:60 秒
- ListItem -> 仰卧起坐:60 秒
- ListItem -> 无
- ListItem -> 无
我可以添加多个列表项,但没有设置重复的活动。
我的数据模型如下所示,listItems
关系定义为 NSOrderedSet
。对于 CodeGen
,我使用 class definition
让 Core Data 自动生成 NSManagedObject
子类。
iFitnessRoutine.xcdatamodeld
我像往常一样设置我的核心数据堆栈,并在必要时用种子数据填充它。
AppDelegate.swift
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
self.addSeedDataIfNecessary()
return true
}
lazy var persistentContainer: NSPersistentContainer { ... }
func addSeedDataIfNecessary() {
// 1. Check if there are fitness circuits.
// Otherwise create "MorningRoutine"
let fitnessCircuitRequest = FitnessCircuit.fetchRequest()
fitnessCircuitRequest.sortDescriptors = []
let fitnessCircuits = try! self.persistentContainer.viewContext.fetch(fitnessCircuitRequest)
if fitnessCircuits.isEmpty {
let fitnessCircuit = FitnessCircuit(context: self.persistentContainer.viewContext)
fitnessCircuit.name = "Morning Routine"
try! self.persistentContainer.viewContext.save()
} else {
print("Fitness Circuits already seeded")
}
// 2. Check if there are activities
// Otherwise create "Jumping Jacks", "Sit-up", and "Lunges"
let activitiesRequest = Activity.fetchRequest()
activitiesRequest.sortDescriptors = []
let activities = try! self.persistentContainer.viewContext.fetch(activitiesRequest)
if activities.isEmpty {
let activityNames = ["Jumping Jacks", "Sit-Ups", "Lunges"]
for activityName in activityNames {
let activity = Activity(context: self.persistentContainer.viewContext)
activity.name = activityName
}
try! self.persistentContainer.viewContext.save()
} else {
print("Activities already seeded")
}
}
在 RoutineTableViewController
中,我创建了 FetchedResultsController
来获取例程,并用其活动填充 table。要添加 activity,我只需创建一个新列表项并为其分配一个随机 activity.
RoutineTableViewController.swift
class FitnessCircuitTableViewController: UITableViewController, NSFetchedResultsControllerDelegate {
var fetchedResultsController: NSFetchedResultsController<FitnessCircuit>!
var persistentContainer: NSPersistentContainer!
var fitnessCircuit: FitnessCircuit! {
return self.fetchedResultsController!.fetchedObjects!.first!
}
//MARK: - Configuration
override func viewDidLoad() {
super.viewDidLoad()
// 1. Grab the persistent container from AppDelegate
self.persistentContainer = (UIApplication.shared.delegate as! AppDelegate).persistentContainer
// 2. Configure FetchedResultsController
let fetchRequest = FitnessCircuit.fetchRequest()
fetchRequest.sortDescriptors = []
self.fetchedResultsController = .init(fetchRequest: fetchRequest, managedObjectContext: self.persistentContainer.viewContext, sectionNameKeyPath: nil, cacheName: nil)
self.fetchedResultsController.delegate = self
// 3. Perform initial fetch
try! self.fetchedResultsController.performFetch()
// 4. Update the title with the circuit's name.
self.navigationItem.title = self.fitnessCircuit!.name
}
//MARK: - FetchedResultsControllerDelegate
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
self.tableView.reloadData()
}
//MARK: - IBActions
@IBAction func addButtonPressed(_ sender: Any) {
// 1. Get all activities
let activityRequest = Activity.fetchRequest()
activityRequest.sortDescriptors = []
let activities = try! self.persistentContainer.viewContext.fetch(activityRequest)
// 2. Create a new list item with a random activity, and save.
let newListItem = ListItem(context: self.persistentContainer.viewContext)
newListItem.activity = activities.randomElement()!
self.fitnessCircuit.addToListItems(newListItem)
try! self.persistentContainer.viewContext.save()
}
//MARK: - TableView
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.fitnessCircuit.listItems?.count ?? 0
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Create a table view cell with index path and activity name
let cell = UITableViewCell()
let listItem = self.listItemForIndexPath(indexPath)
var contentConfig = cell.defaultContentConfiguration()
let activityName = listItem.activity?.name ?? "Unknown Activity"
contentConfig.text = "\(indexPath.row). " + activityName
cell.contentConfiguration = contentConfig
return cell
}
private func listItemForIndexPath(_ indexPath: IndexPath) -> ListItem {
let listItems = self.fitnessCircuit.listItems!.array as! [ListItem]
return listItems[indexPath.row]
}
}
这是我得到的结果:
如您所见,这会产生奇怪的结果。
- 重复活动显示为“未知 Activity”。核心数据不允许它们,即使它们连接到唯一的列表项。
- 无论何时执行此操作,它都会将列表项插入到列表中的随机索引中。否则,它会按预期附加到列表中。
如有任何帮助,我们将不胜感激。
干杯
我认为您在 Activity 实体中设置了唯一约束。在您的代码中看不到它,但如果您在可视化模型编辑器中查看实体,我敢打赌它就在那里。
NSSet 允许您拥有多个具有相同值的项目(如果它们是不同的项目)。也就是说,您可以有多个同名活动,只是不能将多个引用添加到同一个 activity.
这是我刚刚在 Playgrounds 中拼凑的一些示例代码。我使用您的 Core Data 对象模型的简化版本。第一部分只是我用代码构建模型,因为 Playgrounds 没有托管对象模型的可视化编辑器:
import CoreData
// MARK: - Core Data MOM
/// This is a Managed Object Model built in code rather than with the visual editor. The code here corresponds pretty directly to the settings in the visual editor.
let FitnessCircuitDescription:NSEntityDescription = {
let entity = NSEntityDescription()
entity.name = "FitnessCircuit"
entity.managedObjectClassName = "FitnessCircuit"
entity.properties.append({
let property = NSAttributeDescription()
property.name = "name"
property.attributeType = .stringAttributeType
return property
}())
entity.properties.append({
let relationship = NSRelationshipDescription()
relationship.name = "activities"
relationship.isOrdered = true
relationship.deleteRule = .cascadeDeleteRule
return relationship
}())
entity.uniquenessConstraints = [[entity.propertiesByName["name"]!]]
return entity
}()
let FitnessActivityDescription:NSEntityDescription = {
let entity = NSEntityDescription()
entity.name = "FitnessActivity"
entity.managedObjectClassName = "FitnessActivity"
entity.properties.append({
let property = NSAttributeDescription()
property.name = "name"
property.attributeType = .stringAttributeType
return property
}())
entity.properties.append({
let relationship = NSRelationshipDescription()
relationship.name = "fitnessCircuit"
relationship.deleteRule = .nullifyDeleteRule
relationship.maxCount = 1
return relationship
}())
return entity
}()
FitnessCircuitDescription.relationshipsByName["activities"]!.destinationEntity = FitnessActivityDescription
FitnessCircuitDescription.relationshipsByName["activities"]!.inverseRelationship = FitnessActivityDescription.relationshipsByName["fitnessCircuit"]!
FitnessActivityDescription.relationshipsByName["fitnessCircuit"]!.destinationEntity = FitnessCircuitDescription
FitnessActivityDescription.relationshipsByName["fitnessCircuit"]!.inverseRelationship = FitnessCircuitDescription.relationshipsByName["activities"]!
let iFitnessRoutineModel = NSManagedObjectModel()
iFitnessRoutineModel.entities.append(FitnessCircuitDescription)
iFitnessRoutineModel.entities.append(FitnessActivityDescription)
// MARK: - Core Data Classes
/// This stuff is handled for you if you have Codegen set to Class Definition. Don't have that option in Playgrounds.
@objc(FitnessCircuit)
public class FitnessCircuit: NSManagedObject {
@NSManaged var name:String
@NSManaged var activities:NSOrderedSet
@objc(insertObject:inActivitiesAtIndex:)
@NSManaged public func insertIntoActivities(_ value: FitnessActivity, at idx: Int)
@objc(removeObjectFromActivitiesAtIndex:)
@NSManaged public func removeFromActivities(at idx: Int)
@objc(insertActivities:atIndexes:)
@NSManaged public func insertIntoActivities(_ values: [FitnessActivity], at indexes: NSIndexSet)
@objc(removeActivitiesAtIndexes:)
@NSManaged public func removeFromActivities(at indexes: NSIndexSet)
@objc(replaceObjectInActivitiesAtIndex:withObject:)
@NSManaged public func replaceActivities(at idx: Int, with value: FitnessActivity)
@objc(replaceActivitiesAtIndexes:withActivities:)
@NSManaged public func replaceActivities(at indexes: NSIndexSet, with values: [FitnessActivity])
@objc(addActivitiesObject:)
@NSManaged public func addToActivities(_ value: FitnessActivity)
@objc(removeActivitiesObject:)
@NSManaged public func removeFromActivities(_ value: FitnessActivity)
@objc(addActivities:)
@NSManaged public func addToActivities(_ values: NSOrderedSet)
@objc(removeActivities:)
@NSManaged public func removeFromActivities(_ values: NSOrderedSet)
@nonobjc func fetchRequest() -> NSFetchRequest<FitnessCircuit> {
return NSFetchRequest<FitnessCircuit>(entityName: "FitnessCircuit")
}
}
@objc(FitnessActivity)
public class FitnessActivity: NSManagedObject {
@NSManaged var name:String
@NSManaged var fitnessCircuit:FitnessCircuit?
@nonobjc func fetchRequest() -> NSFetchRequest<FitnessActivity> {
return NSFetchRequest<FitnessActivity>(entityName: "FitnessActivity")
}
}
// MARK: - Core Data Extensions
/// Simple extension to give us a typed array to deal with rather than an ordered set.
extension FitnessCircuit {
public dynamic var activityArray: [FitnessActivity] {
return self.activities.array as? [FitnessActivity] ?? []
}
}
// MARK: - Core Data Stack
/// I set this to go to /dev/null so things aren't actually saved to disk. You can set the path to /tmp/iFitnessRoutine if you want to see how it writes data to storage.
let container = NSPersistentContainer(name: "iFitnessRoutine Container", managedObjectModel: iFitnessRoutineModel)
container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? { fatalError("Unresolved error \(error), \(error.userInfo)") }
})
container.viewContext.mergePolicy = NSMergePolicy.mergeByPropertyObjectTrump
// MARK: - Application logic
/// Here's where we start building the objects and connecting them to each other.
let circuit1 = FitnessCircuit(context: container.viewContext)
circuit1.name = "Morning Circuit"
circuit1.addToActivities({ () -> FitnessActivity in
let activity = FitnessActivity(context: container.viewContext)
activity.name = "Jumping Jacks: 60 seconds"
return activity}())
circuit1.addToActivities({ () -> FitnessActivity in
let activity = FitnessActivity(context: container.viewContext)
activity.name = "Lunges: 60 seconds"
return activity}())
circuit1.addToActivities({ () -> FitnessActivity in
let activity = FitnessActivity(context: container.viewContext)
activity.name = "Sit-ups: 60 seconds"
return activity}())
circuit1.addToActivities({ () -> FitnessActivity in
let activity = FitnessActivity(context: container.viewContext)
activity.name = "Jumping Jacks: 60 seconds"
return activity}())
circuit1.addToActivities({ () -> FitnessActivity in
let activity = FitnessActivity(context: container.viewContext)
activity.name = "Sit-ups: 60 seconds"
return activity}())
circuit1.addToActivities({ () -> FitnessActivity in
let activity = FitnessActivity(context: container.viewContext)
activity.name = "Jumping Jacks: 60 seconds"
return activity}())
try! container.viewContext.save()
/// Now, to prove there's nothing up my sleeves, let's pull the data back out of the database and work solely with that, rather than the objects we built above.
let circuitFetch = FitnessCircuit.fetchRequest()
let circuits = try! container.viewContext.fetch(circuitFetch) as! [FitnessCircuit]
for circuit in circuits {
print("Circuit name: \(circuit.name)")
for activity in circuit.activityArray {
print(activity.name)
}
}
当我在 macOS 11.6 上使用 Xcode 13.2.1 运行 时,我得到以下输出:
Circuit name: Morning Circuit
Jumping Jacks: 60 seconds
Lunges: 60 seconds
Sit-ups: 60 seconds
Jumping Jacks: 60 seconds
Sit-ups: 60 seconds
Jumping Jacks: 60 seconds
“Jumping Jacks: 60 seconds”项都是存储在 Core Data 中的不同对象。这是可行的,因为我没有为活动设置任何唯一设置,只为电路设置。
您的 Activity
与 ListItem
的关系是一对一的。
但应该是一对多。当您将 activity
重新分配给“最新”练习时,它会使之前的关系成为 nil
,因为它只能附加到一个 ListItem
.
作为一般规则,每 ?和 !应该在 if else
、if let
或 guard
之前,以便您可以检测到这些东西并对其做出反应。
在 CoreData 中,NSManagedObject
的每个实例都是唯一的。这就是为什么 CoreData 使用 NSSet
(及其对应的有序对象 NSOrderedSet
)来表示集合。但是,我需要一个允许一个项目 多次出现 .
我的直觉是将每个对象包装在一个 ListItem
实体中,并使用 NSOrderedSet
生成列表。由于列表项本身是唯一的,因此对象可以根据需要在列表中出现多次。然而,这会产生意想不到的结果。
示例应用程序
在此示例应用程序 iFitnessRoutine 中,用户可以 select 从活动列表中选择,例如开合跳、仰卧起坐和弓步。然后他们可以构造一个 FitnessCircuit
来创建一个活动列表并在一定时间内执行每个活动。例如:
上午巡回赛:
- 开合跳:60 秒
- 弓步:60 秒
- 仰卧起坐:60 秒
- 开合跳:60 秒
- 仰卧起坐:60 秒
- 开合跳:60 秒
在我的实现中,每个 Activity
都包含在一个 ListItem
中,但是结果会产生如下内容:
上午巡回赛:
- ListItem -> 开合跳:60 秒
- ListItem -> 无
- ListItem -> 弓步:60 秒
- ListItem -> 仰卧起坐:60 秒
- ListItem -> 无
- ListItem -> 无
我可以添加多个列表项,但没有设置重复的活动。
我的数据模型如下所示,listItems
关系定义为 NSOrderedSet
。对于 CodeGen
,我使用 class definition
让 Core Data 自动生成 NSManagedObject
子类。
iFitnessRoutine.xcdatamodeld
我像往常一样设置我的核心数据堆栈,并在必要时用种子数据填充它。
AppDelegate.swift
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
self.addSeedDataIfNecessary()
return true
}
lazy var persistentContainer: NSPersistentContainer { ... }
func addSeedDataIfNecessary() {
// 1. Check if there are fitness circuits.
// Otherwise create "MorningRoutine"
let fitnessCircuitRequest = FitnessCircuit.fetchRequest()
fitnessCircuitRequest.sortDescriptors = []
let fitnessCircuits = try! self.persistentContainer.viewContext.fetch(fitnessCircuitRequest)
if fitnessCircuits.isEmpty {
let fitnessCircuit = FitnessCircuit(context: self.persistentContainer.viewContext)
fitnessCircuit.name = "Morning Routine"
try! self.persistentContainer.viewContext.save()
} else {
print("Fitness Circuits already seeded")
}
// 2. Check if there are activities
// Otherwise create "Jumping Jacks", "Sit-up", and "Lunges"
let activitiesRequest = Activity.fetchRequest()
activitiesRequest.sortDescriptors = []
let activities = try! self.persistentContainer.viewContext.fetch(activitiesRequest)
if activities.isEmpty {
let activityNames = ["Jumping Jacks", "Sit-Ups", "Lunges"]
for activityName in activityNames {
let activity = Activity(context: self.persistentContainer.viewContext)
activity.name = activityName
}
try! self.persistentContainer.viewContext.save()
} else {
print("Activities already seeded")
}
}
在 RoutineTableViewController
中,我创建了 FetchedResultsController
来获取例程,并用其活动填充 table。要添加 activity,我只需创建一个新列表项并为其分配一个随机 activity.
RoutineTableViewController.swift
class FitnessCircuitTableViewController: UITableViewController, NSFetchedResultsControllerDelegate {
var fetchedResultsController: NSFetchedResultsController<FitnessCircuit>!
var persistentContainer: NSPersistentContainer!
var fitnessCircuit: FitnessCircuit! {
return self.fetchedResultsController!.fetchedObjects!.first!
}
//MARK: - Configuration
override func viewDidLoad() {
super.viewDidLoad()
// 1. Grab the persistent container from AppDelegate
self.persistentContainer = (UIApplication.shared.delegate as! AppDelegate).persistentContainer
// 2. Configure FetchedResultsController
let fetchRequest = FitnessCircuit.fetchRequest()
fetchRequest.sortDescriptors = []
self.fetchedResultsController = .init(fetchRequest: fetchRequest, managedObjectContext: self.persistentContainer.viewContext, sectionNameKeyPath: nil, cacheName: nil)
self.fetchedResultsController.delegate = self
// 3. Perform initial fetch
try! self.fetchedResultsController.performFetch()
// 4. Update the title with the circuit's name.
self.navigationItem.title = self.fitnessCircuit!.name
}
//MARK: - FetchedResultsControllerDelegate
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
self.tableView.reloadData()
}
//MARK: - IBActions
@IBAction func addButtonPressed(_ sender: Any) {
// 1. Get all activities
let activityRequest = Activity.fetchRequest()
activityRequest.sortDescriptors = []
let activities = try! self.persistentContainer.viewContext.fetch(activityRequest)
// 2. Create a new list item with a random activity, and save.
let newListItem = ListItem(context: self.persistentContainer.viewContext)
newListItem.activity = activities.randomElement()!
self.fitnessCircuit.addToListItems(newListItem)
try! self.persistentContainer.viewContext.save()
}
//MARK: - TableView
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.fitnessCircuit.listItems?.count ?? 0
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Create a table view cell with index path and activity name
let cell = UITableViewCell()
let listItem = self.listItemForIndexPath(indexPath)
var contentConfig = cell.defaultContentConfiguration()
let activityName = listItem.activity?.name ?? "Unknown Activity"
contentConfig.text = "\(indexPath.row). " + activityName
cell.contentConfiguration = contentConfig
return cell
}
private func listItemForIndexPath(_ indexPath: IndexPath) -> ListItem {
let listItems = self.fitnessCircuit.listItems!.array as! [ListItem]
return listItems[indexPath.row]
}
}
这是我得到的结果:
如您所见,这会产生奇怪的结果。
- 重复活动显示为“未知 Activity”。核心数据不允许它们,即使它们连接到唯一的列表项。
- 无论何时执行此操作,它都会将列表项插入到列表中的随机索引中。否则,它会按预期附加到列表中。
如有任何帮助,我们将不胜感激。 干杯
我认为您在 Activity 实体中设置了唯一约束。在您的代码中看不到它,但如果您在可视化模型编辑器中查看实体,我敢打赌它就在那里。
NSSet 允许您拥有多个具有相同值的项目(如果它们是不同的项目)。也就是说,您可以有多个同名活动,只是不能将多个引用添加到同一个 activity.
这是我刚刚在 Playgrounds 中拼凑的一些示例代码。我使用您的 Core Data 对象模型的简化版本。第一部分只是我用代码构建模型,因为 Playgrounds 没有托管对象模型的可视化编辑器:
import CoreData
// MARK: - Core Data MOM
/// This is a Managed Object Model built in code rather than with the visual editor. The code here corresponds pretty directly to the settings in the visual editor.
let FitnessCircuitDescription:NSEntityDescription = {
let entity = NSEntityDescription()
entity.name = "FitnessCircuit"
entity.managedObjectClassName = "FitnessCircuit"
entity.properties.append({
let property = NSAttributeDescription()
property.name = "name"
property.attributeType = .stringAttributeType
return property
}())
entity.properties.append({
let relationship = NSRelationshipDescription()
relationship.name = "activities"
relationship.isOrdered = true
relationship.deleteRule = .cascadeDeleteRule
return relationship
}())
entity.uniquenessConstraints = [[entity.propertiesByName["name"]!]]
return entity
}()
let FitnessActivityDescription:NSEntityDescription = {
let entity = NSEntityDescription()
entity.name = "FitnessActivity"
entity.managedObjectClassName = "FitnessActivity"
entity.properties.append({
let property = NSAttributeDescription()
property.name = "name"
property.attributeType = .stringAttributeType
return property
}())
entity.properties.append({
let relationship = NSRelationshipDescription()
relationship.name = "fitnessCircuit"
relationship.deleteRule = .nullifyDeleteRule
relationship.maxCount = 1
return relationship
}())
return entity
}()
FitnessCircuitDescription.relationshipsByName["activities"]!.destinationEntity = FitnessActivityDescription
FitnessCircuitDescription.relationshipsByName["activities"]!.inverseRelationship = FitnessActivityDescription.relationshipsByName["fitnessCircuit"]!
FitnessActivityDescription.relationshipsByName["fitnessCircuit"]!.destinationEntity = FitnessCircuitDescription
FitnessActivityDescription.relationshipsByName["fitnessCircuit"]!.inverseRelationship = FitnessCircuitDescription.relationshipsByName["activities"]!
let iFitnessRoutineModel = NSManagedObjectModel()
iFitnessRoutineModel.entities.append(FitnessCircuitDescription)
iFitnessRoutineModel.entities.append(FitnessActivityDescription)
// MARK: - Core Data Classes
/// This stuff is handled for you if you have Codegen set to Class Definition. Don't have that option in Playgrounds.
@objc(FitnessCircuit)
public class FitnessCircuit: NSManagedObject {
@NSManaged var name:String
@NSManaged var activities:NSOrderedSet
@objc(insertObject:inActivitiesAtIndex:)
@NSManaged public func insertIntoActivities(_ value: FitnessActivity, at idx: Int)
@objc(removeObjectFromActivitiesAtIndex:)
@NSManaged public func removeFromActivities(at idx: Int)
@objc(insertActivities:atIndexes:)
@NSManaged public func insertIntoActivities(_ values: [FitnessActivity], at indexes: NSIndexSet)
@objc(removeActivitiesAtIndexes:)
@NSManaged public func removeFromActivities(at indexes: NSIndexSet)
@objc(replaceObjectInActivitiesAtIndex:withObject:)
@NSManaged public func replaceActivities(at idx: Int, with value: FitnessActivity)
@objc(replaceActivitiesAtIndexes:withActivities:)
@NSManaged public func replaceActivities(at indexes: NSIndexSet, with values: [FitnessActivity])
@objc(addActivitiesObject:)
@NSManaged public func addToActivities(_ value: FitnessActivity)
@objc(removeActivitiesObject:)
@NSManaged public func removeFromActivities(_ value: FitnessActivity)
@objc(addActivities:)
@NSManaged public func addToActivities(_ values: NSOrderedSet)
@objc(removeActivities:)
@NSManaged public func removeFromActivities(_ values: NSOrderedSet)
@nonobjc func fetchRequest() -> NSFetchRequest<FitnessCircuit> {
return NSFetchRequest<FitnessCircuit>(entityName: "FitnessCircuit")
}
}
@objc(FitnessActivity)
public class FitnessActivity: NSManagedObject {
@NSManaged var name:String
@NSManaged var fitnessCircuit:FitnessCircuit?
@nonobjc func fetchRequest() -> NSFetchRequest<FitnessActivity> {
return NSFetchRequest<FitnessActivity>(entityName: "FitnessActivity")
}
}
// MARK: - Core Data Extensions
/// Simple extension to give us a typed array to deal with rather than an ordered set.
extension FitnessCircuit {
public dynamic var activityArray: [FitnessActivity] {
return self.activities.array as? [FitnessActivity] ?? []
}
}
// MARK: - Core Data Stack
/// I set this to go to /dev/null so things aren't actually saved to disk. You can set the path to /tmp/iFitnessRoutine if you want to see how it writes data to storage.
let container = NSPersistentContainer(name: "iFitnessRoutine Container", managedObjectModel: iFitnessRoutineModel)
container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? { fatalError("Unresolved error \(error), \(error.userInfo)") }
})
container.viewContext.mergePolicy = NSMergePolicy.mergeByPropertyObjectTrump
// MARK: - Application logic
/// Here's where we start building the objects and connecting them to each other.
let circuit1 = FitnessCircuit(context: container.viewContext)
circuit1.name = "Morning Circuit"
circuit1.addToActivities({ () -> FitnessActivity in
let activity = FitnessActivity(context: container.viewContext)
activity.name = "Jumping Jacks: 60 seconds"
return activity}())
circuit1.addToActivities({ () -> FitnessActivity in
let activity = FitnessActivity(context: container.viewContext)
activity.name = "Lunges: 60 seconds"
return activity}())
circuit1.addToActivities({ () -> FitnessActivity in
let activity = FitnessActivity(context: container.viewContext)
activity.name = "Sit-ups: 60 seconds"
return activity}())
circuit1.addToActivities({ () -> FitnessActivity in
let activity = FitnessActivity(context: container.viewContext)
activity.name = "Jumping Jacks: 60 seconds"
return activity}())
circuit1.addToActivities({ () -> FitnessActivity in
let activity = FitnessActivity(context: container.viewContext)
activity.name = "Sit-ups: 60 seconds"
return activity}())
circuit1.addToActivities({ () -> FitnessActivity in
let activity = FitnessActivity(context: container.viewContext)
activity.name = "Jumping Jacks: 60 seconds"
return activity}())
try! container.viewContext.save()
/// Now, to prove there's nothing up my sleeves, let's pull the data back out of the database and work solely with that, rather than the objects we built above.
let circuitFetch = FitnessCircuit.fetchRequest()
let circuits = try! container.viewContext.fetch(circuitFetch) as! [FitnessCircuit]
for circuit in circuits {
print("Circuit name: \(circuit.name)")
for activity in circuit.activityArray {
print(activity.name)
}
}
当我在 macOS 11.6 上使用 Xcode 13.2.1 运行 时,我得到以下输出:
Circuit name: Morning Circuit
Jumping Jacks: 60 seconds
Lunges: 60 seconds
Sit-ups: 60 seconds
Jumping Jacks: 60 seconds
Sit-ups: 60 seconds
Jumping Jacks: 60 seconds
“Jumping Jacks: 60 seconds”项都是存储在 Core Data 中的不同对象。这是可行的,因为我没有为活动设置任何唯一设置,只为电路设置。
您的 Activity
与 ListItem
的关系是一对一的。
但应该是一对多。当您将 activity
重新分配给“最新”练习时,它会使之前的关系成为 nil
,因为它只能附加到一个 ListItem
.
作为一般规则,每 ?和 !应该在 if else
、if let
或 guard
之前,以便您可以检测到这些东西并对其做出反应。