【发布时间】:2021-12-25 22:47:43
【问题描述】:
在 CoreData 中,NSManagedObject 的每个实例都是唯一的。这就是 CoreData 使用 NSSet(及其有序对应物 NSOrderedSet)来表示集合的原因。但是,我需要一个允许项目多次出现的列表。
我的直觉是将每个对象包装在ListItem 实体中,并使用NSOrderedSet 生成列表。由于列表项本身是唯一的,因此对象可以根据需要在列表中出现多次。然而,这会产生意想不到的结果。
示例应用
在此示例应用程序 iFitnessRoutine 中,用户可以从一系列活动中进行选择,例如起重跳、仰卧起坐和弓步。然后,他们可以构建一个FitnessCircuit 来创建一个活动列表并在一定时间内执行每个活动。例如:
早间巡回赛:
- Jumping Jacks:60 秒
- 弓步:60 秒
- 仰卧起坐:60 秒
- Jumping Jacks:60 秒
- 仰卧起坐:60 秒
- Jumping Jacks:60 秒
在我的实现中,每个Activity 都包装在ListItem 中,但是结果会产生如下内容:
早间巡回赛:
- ListItem -> Jumping Jacks: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 来获取例程,并用它的活动填充表。要添加活动,我只需创建一个新列表项并为其分配一个随机活动。
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]
}
}
这是我得到的结果:
如您所见,这会产生奇怪的结果。
- 重复活动显示为“未知活动”。 Core Data 不允许它们,即使它们连接到唯一的列表项。
- 无论何时执行此操作,它都会将列表项插入到列表中的随机索引中。否则,它会按预期附加到列表中。
任何帮助将不胜感激。 干杯
【问题讨论】: