Joining multiple relationships in swift - ios

please help.
I have 3 NSManaged objects.
say Employee, Payslip, PayItem
Each Employee can have multiple Payslips, each Payslip can have multiple PayItems.
so the relationship is Employee <->> Payslip <<- PayItem
They are all set up as NSManagedOjects.
Then lets say I have 3 instances of each: (imagine I'm initialising each by adding this to the NSManagedObject class:
convenience init(context: NSManagedObjectContext)
{
let entity = NSEntityDescription.entity(forEntityName: <entity>, in: context)!
self.init(entity: entity, insertInto: context)'
}
Then I can declare.
var employee = Employee(context: context)
var payslip = Payslip(context: context)
var payItem = PayItem(context: context)
I can then:
employee.addToPayslip(payslip) //Using the function created for me by default.
But if I try:
payslip.payItem = payItem
I always get the error:
Failed to call designated initializer on NSManagedObject class
'PayItem'
To summarise, I'm trying to link Employee to a payslip, that is one to many, then a payslip to a payitem, that is one to many. Why am I having such a tough time?

So it turned out I had a function that was returning an uninitialised version of PayItem. It took a while to track down, but zapping this resolved my problem.

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.

iOS (Swift): Core data fetching with relationships

If I have a Person entity and a Book entity where a Person can have many Books.
final class Person: NSManagedObject {
#NSManaged public fileprivate(set) var name: String
#NSManaged public fileprivate(set) var books: Set<Book>
}
final class Book: NSManagedObject {
#NSManaged public fileprivate(set) var name: String
#NSManaged public fileprivate(set) var person: Person
static func add(bookNamed name: String, to person: Person) {
guard let context = person.managedObjectContext else { fatalError("Can not obtain managed object Context") }
let book = NSEntityDescription.insertNewObject(forEntityName: "Book", into: context) as Book
book.name = name
book.person = person
}
}
In some UIViewController, I then add a few Books to a Person:
let alex = NSEntityDescription.insertNewObjectForEntityForName("Person", inManagedObjectContext: context) as Person
Alex.name = "Alex"
Book.add("first book", to: alex)
Book.add("second book", to: alex)
Book.add("third book", to: alex)
and then a couple of Books to another Person
let john = NSEntityDescription.insertNewObjectForEntityForName("Person", inManagedObjectContext: context) as Person
john.name = "John"
Book.add("another first book", to: john)
Book.add("another second book", to: john)
I then reload my app and use a fetch request to obtain all the people (i.e. alex and john) in a table view. I then click on alex, which takes me to another view controller, which has an instance of Person that I assign as alex so that I can view the books associated to this instance.
Question Are the Books all loaded into memory even if I have 1000s? Or do I need to perform another fetch to get the Books belonging to alex if I want to display them all? I just want a bit of clarity on what is happening with relationships between two entities as it's slightly confused me recently.
Thanks for any help.
CoreData uses a proxy object model. When you execute a fetch request it transparently creates all directly accessible objects, as proxies. Since they're proxies and not the full object, they are not actually loaded from the data base at creation time, rather they will be loaded from the database when needed (when properties are actually referenced) Likewise, they can be unloaded at any time if they're unmodified and unused.
In this case, that means that your fetch request will create unloaded proxies for each Person. When you display the persons name in the first table view, the data for that Person will be loaded (and cached) in the proxy object. At that time, proxies for each Book referenced by the person will be created as an empty proxy object. When you subsequently select the book and display the details of the book, the actual Book data will be fetched (cached) from the database.
Note: all this is very dependent on the actual datastore in use and is only true of stores, such as sqlite, that allow partial loading. If using an XML or plist store the entire object graph is instantiated and loaded with no empty proxies.

Can't load Core Data in to-many attribute

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?

Adding An Item To An NSSet For A Core Data One To Many Relationship

I have a core data relationship where one entity holds many of another entity. As far as I am aware each instance of the many class is held inside an NSSet? inside the one class. (?)
My question is - what is the best way to add items to this set? I figure this must be a very common problem - but I cannot seem to find an easy method.
This is my attempt: (This is all taken from the one class)
static var timeSlotItems: NSSet? //The Set that holds the many?
...
static func saveTimeSlot(timeSlot: TimeSlot) { //TimeSlot is the many object
retrieveValues()
var timeSlotArray = Array(self.timeSlotItems!)
timeSlotArray.append(timeSlot)
var setTimeSlotItems = Set(timeSlotArray)
self.timeSlotItems = setTimeSlotItems // This is the error line
}
Where retrieveValues() just updates all the coreData values in the class.
TimeSlot is the many object which I want to add.
I get an error on the last line, the error is: "cannot invoke initializer for type Set<_> with an argument of list of type Array"
Am I conceptually wrong at all? Thanks!
Nowadays it is this easy...
For a to-many item named say "Reply", CoreData knows to add a call "addToReplys".
Hence...
p = one Post. your core data Post items have many core data Reply items.
for jr in yourJson {
r = convert jr to a core data Reply
p.addToReplys( r )
so it's just
p.addToReplys( r )
Full example
For one-to-many this is easy. Just use the reverse to-one relationship.
timeSlot.item = self
For many-to-many I use this convenience method:
// Support adding to many-to-many relationships
extension NSManagedObject {
func addObject(value: NSManagedObject, forKey key: String) {
let items = self.mutableSetValueForKey(key)
items.addObject(value)
}
func removeObject(value: NSManagedObject, forKey key: String) {
let items = self.mutableSetValueForKey(key)
items.removeObject(value)
}
}
which is used like this:
self.addObject(slot, forKey:"timeSlotItems")
You've declared both timeSlotItems and saveTimeSlot: as static, so I'm not sure what your intention is there. I suspect it's not what you need.
In the same way that Core Data automatically runtime-generates optimized accessors for attributes, it also generates accessors for relations.
You don't say what the name of the "one" side of the to-many relation is, but if I assume that it's something like Schedule, where Schedule has a to-many relation to TimeSlot called timeSlotItems, then Core Data will runtime-generate the following accessors for you:
class Schedule: NSManagedObject {
#NSManaged public var timeSlotItems: Set<TimeSlot>
#NSManaged public func addTimeSlotItemsObject(value: TimeSlot)
#NSManaged public func removeTimeSlotItemsObject(value: TimeSlot)
#NSManaged public func addTimeSlotItems(values: Set<TimeSlot>)
#NSManaged public func removeTimeSlotItems(values: Set<TimeSlot>)
}

Realm query Object property field by its property

I'm developing an application for iOS using Swift and chose Realm as a database solution for it. I asked one question about Realm and now I have another.
Suppose we have a schema like this:
class Person: Object {
dynamic var id: String = NSUUID().UUIDString
dynamic var name: String?
dynamic var cars: Car?
class Car: Object {
dynamic var name: String?
I have one class (Person) that contains any number of objects of another class (Car). Car that are "linked" with the Person has some properties in context of that Person (and they can be different for same Car for different Persons or for two similar Cars for one Person). Using List<...>() we can not store such properties for each Item, am I right?
If we use Car only for one Person and only once we can create another class that includes only additional properties for Cars and links them with ID of Person plus ID of Car. But it does't work if we have two similar Cars with different additional properties.
So, how I see the solution. Have one table (class) stores ID of Person, ID of one Car and additional properties for this Car. For another Car for the same Person it has the same Person ID, Car ID (same or not) and another additional properties for this instance of a Car.
There is a problem (and a question that I mean). Having that structure I want to query all Cars from that table with their additional properties that have Person ID equals to some_id. How should I do this? Or maybe what another structure (maybe using List<...>) I should have to achieve such kind of behavior?
What is FastList exactly ?
If you want Items to have a property of Lists collection.
You have to redefine your Realm model. something like this.
class Car:Object{
dynamic var createDate: NSDate = NSDate()
}
class Person:Object{
let cars = List<Car>()
}
and query by predicate like this
let realm = Realm()
var ownedCarsFilterByDate = realm.objects(Person).filter("ANY cars.createDate = '\(date)'")
Edited to updated question
Your solution is to create table class, which has 'Person' , 'Car' and 'Context Attribute'.
Your model would be like this
class PersonAndCarRelation:Object{
dynamic var person: Person?
dynamic var car: Car?
dynamic var contextAttribute = ""
}
and you can query all cars associated with person
let personID = "123456789"
let personAndCarArray = realm.objects(PersonAndCarRelation).filter("person.id == \(personID)")
for personAndCar in personAndCarArray{
let personName = personAndCar.person.name
let carName = personAndCar.car.name
let context = personAndCar.contextAttribute
println("I am \(personName). I have a \(carName) with \(context)")
}

Resources