Can't load Core Data in to-many attribute - ios

I have been unable to add and read core data attributes in a one-to-many
relationship. After reading many SO, Apple Docs and other articles I still have
not accomplished this. To make a simple test, I created a standard Master Detail
App with Core Data. This works with no issues for the main entity.
The core data relationship is as shown below.
Here is the code to place some test data into the store. Note that I am
attempting to add the to-many data as a Set for each InvItem and have made the
keywordList attribute Transformable. I thought this was the best approach, but
obviously that is not working here.
func seedInvItems(num : Int) {
for index in 1...num {
let newManagedObject = NSEntityDescription.insertNewObjectForEntityForName("InvItem", inManagedObjectContext: kAppDelegate.managedObjectContext) as! InvItem
newManagedObject.name = "myName\(index)"
newManagedObject.category1 = "myCategory1x\(index)"
newManagedObject.compartment = "myCompartment\(index)"
newManagedObject.entryDate = NSDate()
//and for the one-to-many relationship
var myStoreKeywordSet = Set<String>()
myStoreKeywordSet = ["one","two","three"]
// do an insert just for grins
myStoreKeywordSet.insert("four")
let newManagedObject2 = NSEntityDescription.insertNewObjectForEntityForName("InvItemKeyword", inManagedObjectContext: kAppDelegate.managedObjectContext) as! InvItemKeyword
newManagedObject2.keywordList = myStoreKeywordSet
}//for in
kAppDelegate.saveContext()
let fetchRequestTest = NSFetchRequest(entityName: "InvItem")
let sorter : NSSortDescriptor = NSSortDescriptor(key: "name", ascending: true)
fetchRequestTest.sortDescriptors = [sorter]
var resultsTest : [InvItem]?
do {
resultsTest = try kAppDelegate.managedObjectContext.executeFetchRequest(fetchRequestTest) as? [InvItem]
for object in resultsTest! {
let myRetrieveKeywordSet = object.invitemkeywords
print(myRetrieveKeywordSet)
}//for in
} catch let error as NSError {
//what happens on failure
print("And the executeFetchRequest error is \(error.localizedDescription)")
}//do catch
}//seedInvItems
For completeness, I did create the NSManagedObject subclasses for the Core
Data Entities.
When running the app, the master detail for InvItem behaves as expected but I
get no storage of the to-many items.
Here is the console log (for one InvItem):
Optional(Relationship 'invitemkeywords' fault on managed object (0x7f985a62bb70) (entity: InvItem; id: 0xd000000000700000 ; data: {
category1 = myCategory1x3;
compartment = myCompartment3;
entryDate = "2016-02-09 02:10:21 +0000";
invitemkeywords = "";
name = myName3;
}))
Looking at the database, there is no data for the keywordList.
Any help would be appreciated. Xcode 7.2.1 IOS 9.2

You need to set the relationship from newManagedObject to newManagedObject2. In fact, because the relationship is one-many, it is easier to set the to-one relationship:
newManagedObject2.invitem = newManagedObject
and let CoreData handle the inverse, to-many relationship.
As to the keyWordList not being populated, I wonder whether your SQL browser is unable to decode CoreData's NSSet? I also wonder whether you need to have keywordList as an NSSet? The relationship from InvItem is already to-many, so your model implies that each InvItem can have many InvItemKeywords, each of which holds many keywords in its keywordList attribute: is that what you intended?

Related

How to fetch relationship entities when performing a core data migration?

I am performing a core data migration. I have this subclass for my migration:
class TagtoSkillMigrationPolicyV1toV2: NSEntityMigrationPolicy {
override func createDestinationInstances( forSource sInstance: NSManagedObject, in mapping: NSEntityMapping, manager: NSMigrationManager) throws {
// 1 - create destination instance of skill and skillmetadata
let skillEntityDescription = NSEntityDescription.entity( forEntityName: "Skill",in: manager.destinationContext)
let newSkill = Skill(entity: skillEntityDescription!, insertInto: manager.destinationContext)
let skillMetaDataDescription = NSEntityDescription.entity(forEntityName: "SkillMetaData", in: manager.destinationContext)
newSkill.metaData = SkillMetaData(entity: skillMetaDataDescription!, insertInto: manager.destinationContext)
var posts = sInstance.value(forKey: "posts")
posts.sort {
($0.timeStamp! as Date) < ($1.timeStamp! as Date)
}
if let mostRecentThumbnail = posts.first?.postImage {
let thumbnail = Utils.makeThusmbnail(url: URL(string: mostRecentThumbnail)!, for: 150.0)
newSkill.metaData?.thumbnail = thumbnail?.pngData()
}
if let name = sInstance.value(forKey: "name"){
newSkill.setValue(name, forKey: "name")
}
manager.associate(sourceInstance: sInstance, withDestinationInstance: newSkill, for: mapping)
}
}
This custom migration is from my old Tag entity to my new Skill entity. For each Tag, it creates a skill entity and a skillmetaData entity that it attaches to it. There are no entries in the .xcmappingmodel file, as I am doing everything manually - please let me know if I still need entries there.
Right now, when it runs, it gives a signal SIGABRT:
Could not cast value of type '_NSFaultingMutableOrderedSet' (0x7fff87c44e80) to 'NSArray' (0x7fff87c51fb0).
2020-04-14 15:55:40.108927-0700 SweatNetOffline[28046:3645007] Could not cast value of type '_NSFaultingMutableOrderedSet' (0x7fff87c44e80) to 'NSArray' (0x7fff87c51fb0).
When I set a breakpoint and inspect sInstance.value(forKey: "posts"), I get
▿ Optional<Any>
- some : Relationship 'posts' fault on managed object (0x6000010dcf00) <NSManagedObject: 0x6000010dcf00> (entity: Tag; id: 0xdf4b05c2e82b2c4e <x-coredata://A6586C18-60C3-40E7-B469-293A50EB5728/Tag/p1>; data: {
latestUpdate = "2011-03-13 00:17:25 +0000";
name = flowers;
posts = "<relationship fault: 0x600002607f40 'posts'>";
uuid = "4509C1B3-7D0F-4BBD-AA0B-7C7BC848DA80";
})
so its not entirely loading those posts - its giving that fault there.
The goal is to get the thumbnail from the latest of those posts. How can I access this relationship item?
I found a way to do it using
let lastThumbnail = sInstance.mutableOrderedSetValue(forKeyPath: "posts.postImage").lastObject
Reading this helped a lot. My understanding is at this point in the migration, we are dealing with NSManagedObjects and never the actual Model which would have attributes like "posts" that it usually does in core data.
In Objective-C especially and swift to some extent, NSObjects are accessed with key-value coding. They are accessed indirectly - meaning instead of reading the object itself, you are reading what the NSMutableOrderedSet wants to show you for that key - so it can do stuff to it before presenting it. Thus you can't for instance, fetch just "forKey: posts" and then expect posts to have the attributes your post usually does. Anyone is welcome to flesh out this explanation if they know more :).
One thing I was trying to do is to fetch by that key-path in a sortable way - so that I can find the post that has the most recent timestamp and then use that associated thumbnail. Instead I am using .lastObject which works because this is an NSMutableOrderedSet and not an NSMutableSet because my original model specified this relationship to be an ordered relationship. This means that it will pick the last inserted item which is ok for me for now. Still interested in a way to filter that result though if anyone has advice.

Getting relationship fault while accessing a child object in core data

I have a two core data entities Contact and Group. Groups have two attributes existingUsers and nonExistingUsers which are of type Contact and toMany relationship
I'm trying to fetch group attributes using groupId as a predicate. This is how I'm fetching group details
let requestForGroups = NSFetchRequest<NSFetchRequestResult>(entityName: "Group")
requestForGroups.returnsObjectsAsFaults = false
requestForGroups.predicate = NSPredicate(format: "groupId == %#", (groupObject?.groupId)!)
requestForGroups.relationshipKeyPathsForPrefetching = ["existingUsers", "nonExistingUsers"]
do {
let results = try managedObjectContext.fetch(requestForGroups) as! [Group]
if results.count > 0 {
print(results.first)
var group: Group!
group = results.first
for users in group.existingUsers?.allObjects as! [Contact] {
print(users)
}
for users in group.nonExistingUsers?.allObjects as! [Contact] {
print(users)
}
}
} catch let error as NSError {
print("Error: \(error) " +
"description \(error.localizedDescription)")
}
When I try to print the Group object, I get the following relationship fault
Optional(<Foodvite.Group: 0x1706801e0> (entity: Group; id: 0xd0000000018c0000 <x-coredata://E4C6CB69-7192-435C-9E2A-534FCFC563F5/Group/p99> ; data: {
existingUsers = "<relationship fault: 0x17063b620 'existingUsers'>";
groupCategory = nil;
groupIconUrl = nil;
groupId = "8ba3e576-edf7-41fa-abd9-df80c4c693f2";
groupName = Friends;
groupOwnerId = "7fa49a4e-2cbf-4c77-8868-bc97ad89881b";
nonExistingUsers = "<relationship fault: 0x17063b600 'nonExistingUsers'>";
}))
Can someone suggest a way to access existingUsers and nonExistingUsers data?
This is correct as Coredata relationships are loaded lazily by default.
Here is Apple documentation on the same.
Managed objects typically represent data held in a persistent store.
In some situations a managed object may be a fault—an object whose
property values have not yet been loaded from the external data store.
Faulting reduces the amount of memory your application consumes.
To access existingUsers and nonExistingUsers data, you need to explicitly ask(if they are not arrays):
print(group!.existingUsers.<attributeName>)
print(group!.nonExistingUsers.<attributeName>)
If your relationships are arrays:
if let first = group?.existingUsers.first {
print(first.<attributeName>)
}
Read this for better understanding.
A fault is not an error. It is a performance consideration where data from the persistent store is not loaded unless you actually need it. This is especially true for a fetched object that has a property that represents a to-many relationship to potentially thousands of other entities. It would be silly to load thousands of other objects in memory if you aren't even going to use them.
In your case, existingUsers and nonExistingUsers are probably both NSSets. You can access the elements within them like so:
//assuming "group" is an NSManagedObject
for user in group.existingUsers! {
user.property = someValue
if user.otherProperty == ...
}
Upon accessing each user in the set of existingUsers, the NSManagedObjectContext will automatically populate the object's properties with the values retrieved from the persistent store. In short, the expected values for the properties of each user will properly be returned/set when you access or assign them.

Core Data model design or architecture

I'm planning to use Core Data to store my app's data. However, when I tried to design the architecture of the model entities, I found that the most core data tutorials mixed the "fetch data"、"insert data"、"delete data" with ViewControllers. Doesn't such operations be put in model's implementation according to the information system design principle??
For example, there is a base ModelClass:
class BaseModel {
var modelName: String!
// insert
func insertNewObjectToModel(managedContext:NSManagedObjectContext)->BaseModel {
return NSEntityDescription.insertNewObjectForEntityForName(modelName, inManagedObjectContext: managedContext) as! BaseModel
}
// get
func fetchObject(predicate: NSPredicate)...
// delete
func deleteObject(object:BaseModel)...
}
And then I have a model "Student" inherit from BaseModel class, so it inherits its methods of inserting, querying, deleting and so on. Then in ViewController class, if user wants to add a new object, just call student.insertNewObject(context).
I'm really confused about whether such design or encapsulation works. I have read Ray's 《Core Data by Tutorials》, and also read the Apple's Core Data examples. But it seemed that they all put model related operations in ViewController.
For example, I picked a few lines of code from Ray's book:
func populateDealsCountLabel() {
// 1
let fetchRequest = NSFetchRequest(entityName: "Venue")
fetchRequest.resultType = .DictionaryResultType
// 2
let sumExpressionDesc = NSExpressionDescription()
sumExpressionDesc.name = "sumDeals"
// 3
sumExpressionDesc.expression = NSExpression(forFunction: "sum:", arguments: [NSExpression(forKeyPath: "specialCount")])
sumExpressionDesc.expressionResultType = .Integer32AttributeType
// 4
fetchRequest.propertiesToFetch = [sumExpressionDesc]
// 5
do {
let results = try coreDataStack.context.executeFetchRequest(fetchRequest) as! [NSDictionary]
let resultDict = results.first!
let numDeals = resultDict["sumDeals"]
numDealsLabel.text = "\(numDeals!) total deals"
} catch let error as NSError {
print("Could not fetch \(error), \(error.userInfo)")
}
}
English is not my tongue language, so I'm not sure if you seeing the question understand what my description want to express. But I'm hoping for your comments and answers! Thanks in advance!

How to pass NSManagedObject to NSPredicate and how to save one-to-many Entities

I have a simple iOS8 Core Data application that lists information about a patient in a Master Detail structure. Xcode 6.3.1
That piece of the app works fine - a tableView of Patients with details on each Patient in a second view controller. Core Data has just two entities - Patient and Note and I set a one-to-many relationship. Each Patient can have many Notes, but each Note belongs to only one Patient.
On the Patient detail page I have a button that launches a second tableview-viewcontroller pair for the list of Notes and Note detail that belongs to that Patient.
Using a fetchedresultscontroller, the data is correctly populated in the Patient table view and the Patient Detail view. However, I can't seem to tie the Notes to an individual Patient. My attempts to retrieve notes belonging to an individual Patient have not worked either, but I suspect that is due to the fact that I haven't linked the Notes to a Patient object when saving the Note.
The Note save code:
func addNewNote() {
makeEntryFieldsEnabledYES()
//the Patient that owns this record is available as self.patientPassedIn
//println("patientPassedIn is: \(patientPassedIn)")
let context = kAppDelegate.managedObjectContext
let entity = NSEntityDescription.entityForName("Note", inManagedObjectContext: kAppDelegate.managedObjectContext!)
var noteToAdd = NSManagedObject(entity: entity!, insertIntoManagedObjectContext: kAppDelegate.managedObjectContext!) as! Note
noteToAdd.setValue(self.titleField.text, forKey: "title")
noteToAdd.setValue(self.noteCategoryField.text, forKey: "noteCategory")
noteToAdd.info = informationView.text
noteToAdd.creationDate = NSDate()
kAppDelegate.saveContext()
performSegueWithIdentifier("unwindToNotesTableViewController", sender: self)
}//addNewNote
I do pass the relevant Patient object to the Note view controller, but I have not been able to link the note to
that object.
How does one construct a save operation for a to-many entity so that the many Note objects relate only to the single
Patient object? Then how do you construct the fetch request to fetch only the Note objects that belong to
a given Patient? I want to pass a Patient object to the predicate and retrieve the appropriate notes.
The Note fetching code:
var fetchedResultsController: NSFetchedResultsController {
managedObjectContext = kAppDelegate.managedObjectContext
if myFetchedResultsController != nil {
return myFetchedResultsController!
}
let fetchRequest = NSFetchRequest()
// Check this: auto generate didn't work - fix that
let entity = NSEntityDescription.entityForName("Note", inManagedObjectContext: managedObjectContext)
fetchRequest.entity = entity
fetchRequest.fetchBatchSize = 20
let sortDescriptor = NSSortDescriptor(key: "creationDate", ascending: false)
let sortDescriptors = [sortDescriptor]
fetchRequest.sortDescriptors = [sortDescriptor]
//prove that you have passed the Patient object
var testString = passedInPatient?.lastName
println(testString!)
//try this for only retrieving the notes for the given patient
//this does not work
var notePredicate = NSPredicate(format: "passedInPatient")
fetchRequest.predicate = notePredicate
//
//try above
let aFetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: managedObjectContext, sectionNameKeyPath: nil, cacheName: nil)
var countError : NSError? = nil
var count = managedObjectContext.countForFetchRequest(fetchRequest, error: &countError)
println("The count is \(count)")
aFetchedResultsController.delegate = self
myFetchedResultsController = aFetchedResultsController
var error: NSError? = nil
if !myFetchedResultsController!.performFetch(&error) {
//create an alert if the fetching fails
println("Unresolved error \(error), \(error!.userInfo)")
abort()
}//if !
return myFetchedResultsController!
}//var fetchedResultsController
I do pass the relevant Patient object to the Note view controller, but I have not been able to link the note to that object.
How does one construct a save operation for a to-many entity so that the many Note objects relate only to the single Patient object? Then how do you construct the fetch request to fetch only the Note objects that belong to a given Patient? I want to pass a Patient object to the predicate and retrieve the appropriate notes.
In your addNewNote function, add another line to set the relationship (it is easiest to set the to-one relationship; CoreData will automatically set the inverse relationship for you):
noteToAdd.patient = patientPassedIn
In your NSFetchedResultsController, the format you need for your predicate is like this:
var notePredicate = NSPredicate(format: "patient == %#", passedInPatient)
fetchRequest.predicate = notePredicate

Sort entities based on fetched property

In my application (written in Swift) I use a model for Core Data with two configurations, each of these uses different save path (one is read-only in the application bundle and another is in the Documents folder). I set in one of two configurations an entity "Domanda" with the following attributes:
In the other configuration I set an entity "StatDomanda" with the following attributes:
"StatDomanda" is used for user data, while "Domanda" is used as preloaded source. I know that I can't set up a relationship between the two entities (two configurations), but I would order the "Domanda" entity based on the "corrette" attribute of the "StatDomanda" entity where StatDomanda.numero == Domanda.numero and StatDomanda .argomento == Domanda.argomento.nomeArgomento.
Not all "Domanda" objects have corresponding "StatDomanda" objects.
I set a fetched property (called "statistiche") in the "Domanda" entity with this predicate
$FETCH_SOURCE.numero = numero AND
$FETCH_SOURCE.argomento.nomeArgomento = argomento
but I do not understand how to use it to order the objects.
Thanks for the help!
I found a solution, I don't know if is the best way but it works!
In my core data manager class (ModelManager) I have this function used to fetch all the "Domanda" entities:
func getAllQuestions() -> [Domanda] {
let fetchRequest = NSFetchRequest(entityName: "Domanda")
let fetchResults = managedObjectContext!.executeFetchRequest(fetchRequest, error: nil) as? [Domanda]
return fetchResults!
}
In my ViewController I use this code to obtain the sorted array of "Domanda" entities based on attribute "corrette" of "statistiche" fetched property:
override func viewDidLoad() {
super.viewDidLoad()
var arrayDomande = ModelManager.instance.getAllQuestions()
var sortedArrayDomande = self.sortArray(arrayToSort: arrayDomande)
}
func sortArray(arrayToSort sarray: Array<Domanda>) -> Array<Domanda> {
// 1 - Filter the array to find only questions that have stats
var onlyAnswered = sarray.filter({($0.valueForKey("statistiche") as [Domanda]).count == 1})
// 2 - Sort the array based on fetched property
var sortedArrayDomande = sorted(onlyAnswered, {
let first = ($0.valueForKey("statistiche") as [StatDomanda])[0]
let firstC = first.corrette.integerValue
let second = ($1.valueForKey("statistiche") as [StatDomanda])[0]
let secondC = second.corrette.integerValue
return firstC > secondC
})
return sortedArrayDomande
}
or even in condensed form:
// 2 - Sort the array based on fetched property
var sortedArrayDomande = sorted(onlyAnswered, {($0.valueForKey("statistiche") as [StatDomanda])[0].corrette.integerValue > ($1.valueForKey("statistiche") as [StatDomanda])[0].corrette.integerValue})
Goodbye guys, see you next time!

Resources