在 CoreData 中,每個實體NSManagedObject都是唯一的。這就是 CoreData 使用NSSet(及其有序對應物NSOrderedSet)來表示集合的原因。但是,我需要一個允許一個專案出現不止一次的串列。
我的直覺是將每個物件包裝在一個ListItem物體中,并用于NSOrderedSet生成串列。由于串列項本身是唯一的,物件可以根據需要在串列中出現多次。然而,這會產生意想不到的結果。
示例應用程式
在這個示例應用程式 iFitnessRoutine 中,用戶可以從一系列活動中進行選擇,例如千斤頂、仰臥起坐和弓步。然后,他們可以構建一個FitnessCircuit以創建活動串列并在特定持續時間內執行每個活動。例如:
晨間賽道:
- 跳躍千斤頂:60 秒
- 弓步:60 秒
- 仰臥起坐:60秒
- 跳躍千斤頂:60 秒
- 仰臥起坐:60秒
- 跳躍千斤頂:60 秒
在我的實作中,每個Activity都包含在 a 中ListItem,但是結果產生如下所示:
晨間賽道:
- ListItem -> Jumping Jacks:60 秒
- 串列項 -> 零
- ListItem -> 弓步:60 秒
- ListItem -> 仰臥起坐:60 秒
- 串列項 -> 零
- 串列項 -> 零
我可以添加多個串列項,但沒有設定重復的活動。
我的資料模型看起來像這樣,listItems關系定義為NSOrderedSet. 對于CodeGen,我class definition曾經讓 Core Data 自動生成NSManagedObject子類。
iFitnessRoutine.xcdatamodeld

我像往常一樣設定我的 Core Data 堆疊,并在必要時用種子資料填充它。
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 不允許它們,即使它們連接到唯一的串列項。
- 無論何時執行此操作,它都會將串列項插入到串列中的隨機索引中。否則,它會按預期附加到串列中。
任何幫助將不勝感激。干杯
uj5u.com熱心網友回復:
你Activity的ListItem關系是一對一的。
但應該是一對多。當您將 重新分配activity給“最新”練習時,它會建立以前的關系,nil因為它只能附加到一個ListItem.
作為一般規則,每 ? 和 !前面應該有一個if else,if let或者guard這樣你就可以檢測到這些東西并做出反應。
uj5u.com熱心網友回復:
我認為您在 Activity 物體中設定了一個獨特的約束。在您的代碼中看不到它,但是如果您在可視化模型編輯器中查看物體,我敢打賭它就在那里。
NSSet 允許您擁有多個具有相同值的專案,如果它們是不同的專案。也就是說,您可以有多個同名的活動,只是不能對同一個活動添加多個參考。
這是我剛剛在 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 中的不同物件。這是有效的,因為我沒有任何獨特的活動設定,僅適用于電路。
轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/393985.html
上一篇:痞子衡嵌入式:把玩i.MXRT1062 TencentOS Tiny EVB_AIoT開發板(2) - 在Flash除錯及離線啟動
