iOS: core data to many relationship - ios

I have a doubt how to manage a "to many relationship" in core data.
In my example I have the main identity 'Struct' that have a "to many relationship" with another identity called 'Id_loc'
Then, I have this object in a JSON file that is a Struct identity:
{"id":"s1",
"n":"Name Struct",
"id_loc":["l1","l2"]} //id_loc can contain many element
when I parse this object I have id_loc as an array.
Inside Struct class I have two methods:
- (void)addLocObject:(Id_loc *)value;
- (void)addLoc:(NSSet *)values;
then I do this to store id_loc array inside:
Struct *struct = [NSEntityDescription insertNewObjectForEntityForName:#"Struct" inManagedObjectContext:context];
NSArray *array_loc = [element objectForKey:#"id_loc"];
NSSet *set = [NSSet setWithArray:array_loc];
[struct addLoc:set];
Is it a right way?
Is it not necessary to call this?
Id_loc *loc = [NSEntityDescription insertNewObjectForEntityForName:#"Id_loc" inManagedObjectContext:context];
EDIT
Is it the right answer?
Struct *struct = [NSEntityDescription insertNewObjectForEntityForName:#"Struct" inManagedObjectContext:context];
NSArray *array_loc = [element objectForKey:#"id_loc"];
for (id loc in array_loc){
Id_loc *loc = [NSEntityDescription insertNewObjectForEntityForName:#"Id_loc" inManagedObjectContext:context];
loc.ident = loc;
[struct addLocObject:loc];
}

You cannot really save an NSSet into the CoreData just like that. NSSet contains other CoreData entities only which are related to your main object.
To save an array with data you need to use NSData property and use NSKeyedArchiver to archive your NSArray with NSStrings.
However even it's simplest solution there are some limitations. For example you won't be able to use and predicates on those properties. Therefore I would recommend to make another entity which is "Location" and create a location objects based on those "l1", "l2" values.
Yes you use addLocObject: or if you want to add multiple ones you use - (void)addLoc:(NSSet *)values; But you have to create those objects - create them in core data withing your context and than add it to main object.
Probably if you have ID you want also to select first existing locations and create them only if the don't exist.

Related

IOS/Objective-C/CoreData: Adding new managed objects to an MOC

I want to add some new managed objects to an entity in core data. These managed objects are also represented by a class
Is it necessary to do the following each time?
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Steps" inManagedObjectContext:self.managedObjectContext];
// Initialize Record
NSManagedObject *blankStep = [[NSManagedObject alloc] initWithEntity:entity insertIntoManagedObjectContext:self.managedObjectContext];
Alternatively, if I have an array of these objects, can I merely add a new object to the array and then save the context?
NSMutableArray <Steps *> *theNewSteps = [NSMutableArray arrayWithCapacity:20];
Steps *blankStep = [[Steps* alloc] init];
[theNewSteps addObject: blankStep];
Am confused about what is going on here.
Is it necessary to do the following each time?
Yes, either that or this:
NSManagedObject *blankStep = [NSEntityDescription insertNewObjectForEntityForName:#"Steps" inManagedObjectContext: self.managedObjectContext];
Either way instantiates a new object and inserts it into the managed object context.
Alternatively, if I have an array of these objects, can I merely add a new object to the array and then save the context?
No. Core Data doesn't know about your array, so it doesn't know if you change that array. Also, this line:
Steps *blankStep = [[Steps alloc] init];
Will fail because it tries to create a new Steps instance but doesn't call the designated initializer for the class. You can't use init for managed objects, you need to use either your first code snippet or the version I mentioned above.
To add new managed object use the below code.
Steps *steps = [NSEntityDescription insertNewObjectForEntityForName:#"Steps" inManagedObjectContext:[self managedObjectContext];
//now you have the Steps object, if want to add values to the object you can add also.
//now save the moc
still have any doubt plz let me know.

Insert JSON array into the Core Data entities

I have a JSON returned by REST API to my already existing app that I am trying to fix. I am fairly new to objective C.
[
{
"Activities":"
[
{
"activityid":845,
"activityname":"Registration and networking breakfast",
"actvitydesc":"Registration and networking breakfast",
},
{
"activityid":846,
"activityname":"Plenary session: The Workforce Tsunami",
"actvitydesc":"It's Time to Rethink Talent
}
}
]
There is a core data entity Activity in my app, which contains the following attributes
Activityid activityname activitydesc
How can I insert the JSON data inside my core data entity? Is there any need to create model class to do that? Can I insert my json data directly into core data without creating model objects?
If you already have the entity called Activity you can use the NSManagedObject class to set the value for an Attribute. Try this
NSManagedObject *managedObject = [NSEntityDescription insertNewObjectForEntityForName:#"Activity" inManagedObjectContext:_managedObjectContext];
[managedObject setValue:[NSNumber numberWithInteger:4711] forKey:#"activityid"];
and so on...
You can also create the class by the Classgenerator of CoreData in XCode 8 there are multiple ways. Defaultly the class is generated automatically since XCode 8. If you don't like this you can disable it and generate the class manually. Just go to the CoreData Model --> Editor --> Create NSManagedObject Subclass. Note you have to deactivate the automatic code generation before. If you don't do that, you will become errors while building the project.
If the name of the JSON Attribute is equal to the name of the CoreData Attribute you can also loop over the Dictionary like this:
NSArray *wrapper = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingAllowFragments error:nil];
for(NSDictionary *dict in wrapper){
NSArray *activities = [dict objectForKey:#"Activities"];
for(NSDictionary *activity in activities){
NSManagedObject *managedObject = [NSEntityDescription insertNewObjectForEntityForName:#"Activity" inManagedObjectContext:_managedObjectContext];
// [managedObject setValue:[NSNumber numberWithInteger:4711] forKey:#"activityid"];
for(NSString *attributeName in activity)
[managedObject setValue:[activity objectForKey:attributeName] forKey:attributeName];
}
}
Hope that helps you...
To insert anything to core data you need to generate models. Create core data model editor (if you don't have one) and add Entities. There is lots of tutorials on the web how to do it.

Storing RLMResults instead of re-fetching

I've just started using Realm for Objective-C, I've used Realm for Swift before and I can't remember having any problems with it.
I want to store the fetched objects and convert them to RLMObjects/NSMutableArrays and have them as ViewController's properties, so I won't have to fetch them again using predicates and descriptors and getting them through a loop to distinct them, because there are lots of data to fetch.
RLMResults *results = [Sales allObjects];
NSMutableArray<NSString *> resultsIDs* = [[NSMutableArray alloc] init];
NSMutableArray<Sales *> *uniqueSales = [[NSMutableArray alloc] init];
for (Sales *sale in results) {
NSString *id = sale.id;
if (![resultsIDs id]) {
[resultsIDs id];
[uniqueSales addObject:sale];
}
}
self.distinctProducts = uniqueSales;
I know RLMObjects are not thread-safe but since I add different data to different object models concurrently (and only once when the app launches), keeping a reference to the threads does not seem to be a good idea to me.
Just to clarify, RLMResults objects are live, meaning that if their underlying data is changed, they get updated automatically. As a result, there's never really a need to re-query or refresh the same results object.
If you still want to store a custom-sorted list of RLMObject instances, the most efficient way would be to create a separate Realm model class that has an RLMArray property in which you can then save all of your objects.
For example, maybe calling it a SalesList object:
RLM_ARRAY_TYPE(Sales)
#interface SalesList : RLMObject
#property RLMArray<Sales *><Sales> *sortedSales;
#end
It would then be a matter of simply keeping one SalesList object, and adding all of the Sales objects you sorted into the sortedSales array.

Save to more than one entity

For a detail view I would like to let the user leave notes for each item. The app is for a data-driven website. In the web version, the web app stores notes in a separate table with a field for the itemid.
In Core Data I have an entity of items and another entity of notes. The notes entity has an attribute called itemid. When user creates a note the first time, it stores the itemid in the note record.
My question is when you pull up the item for editing how can you simultaneously pull up the right note based on the note having a certain itemid?
In a database situation you could do a join, or from a web page you could make two separate requests to the two tables but I am somewhat flummoxed by how to do this with Core Data.
Do you have to put a relationship to the note and therefore have the noteid in the item row?
If so would you be able to access the note attribute using the item object?
Thanks in advance for any suggestions.
This is what I am using to save information. I just don't know how to make sure I'm saving it for right note.
self.managedObjectContext = [IDModel sharedInstance].managedObjectContext;
NSString *noteText = _notesView.text;
NSNumber *itemId = self.item.itemid;
// Populate Record
[self.note setValue:noteText forKey:#"note"];
[self.note setValue:itemId forKey:#"itemid"];
Model (simplified):
Item:
name NSString
itemid: Integer 64
Note:
note NSString
noteid: Integer 64
itemid: Integer 64
Edit:
Code to try to link note and item while creating both...
//in save method
// Create Entity
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Notes" inManagedObjectContext:self.managedObjectContext];
// Initialize New Record ie newNote
NSManagedObject *record = [[NSManagedObject alloc] initWithEntity:entity insertIntoManagedObjectContext:self.managedObjectContext];
// Populate Record
[record setValue:note forKey:#"note"];
[record setValue:localid forKey:#"localnid"];
// Save Record
NSError *error = nil;
if ([self.managedObjectContext save:&error]) {
// If note saved, save new item…
if (itemlength>1) {
Items *newItem = [NSEntityDescription insertNewObjectForEntityForName:#“Item” inManagedObjectContext:self.managedObjectContext];
newItem.item = item;
newItem.note = self.note
//This is null as note does not seem to pick up newly created note.
if (![self.managedObjectContext save:&error]) {
NSLog(#"Error: %#", [error localizedDescription]);
}
}
Yes, you should use a relationship between the Item and Note entities. To create a relationship just Ctrl-drag from one entity to the other in the data model editor. Note that Xcode automatically adds an inverse relationship:
I've renamed the relationships for clarity - you can tailor the details of the relationship (name, one-one v one-many, delete rule, etc) in the panel on the right. In the example above, the Item entity has three properties: 2 attributes and 1 relationship. Given an Item object, say myItem, the values for these properties can be accessed using the Key Value Coding methods: valueForKey: and setValue:forKey:. For example, if attribute is defined as a string:
NSString *myStringValue = [myItem valueForKey:#"attribute"];
[myItem setValue:#"new value for attribute" forKey:#"attribute"];
That's very long-winded. So to make life easier, use the "Create NSManagedObject subclass..." option. Xcode will configure each entity to be a subclass of NSManagedObject and will create new class files (.h/.m or .swift) with details of the properties. For the example Item:
#property (nullable, nonatomic, retain) NSString *attribute;
#property (nullable, nonatomic, retain) NSString *attribute1;
#property (nullable, nonatomic, retain) Note *note;
The thing to realise is that the note relationship is of class Note. It's not a foreign key, or a noteid, that you have to use to lookup the corresponding Note object. It is the Note object. Under the hood, CoreData is adding primary keys and foreign keys to the underlying tables, but all that aggravation is abstracted away.
Having created the subclasses, you can use the dot-accessors for the object properties:
NSString *myStringValue = myItem.attribute;
myItem.attribute = #"new value for attribute";
For the relationship, if you have an Item object called myItem and a Note object called myNote, you can set the relationship value with:
myItem.note = myNote;
or equivalently:
myNote.item = myItem;
(Note: use one or the other, not both; CoreData automatically sets inverse relationships for you).
Now, you have the added complication of a web server from which the Item objects and Note objects are downloaded. And on your web server, your Notes table has a field for the itemid, which is used to link Items and Notes. At some point, you want to link up Note objects and Item objects using the itemid. The usual approach would be to do it once (as soon as the CoreData objects are synchronised from the server), set the relationship accordingly, and thenceforth use the relationship rather than the itemid to get the note for a given item. For example, if you are creating a new Note object, and the itemid from the server is "1234", you might do this:
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"Item"];
fetchRequest.predicate = [NSPredicate predicateWithFormat:#"itemid == %#", #"1234"];
NSError *error;
NSArray *results = [context executeFetchRequest:fetchRequest error:&error];
// should check for nil results/error
if (results.count > 0) {
// found (at least) one Item with itemid == #"1234"
// use the first to set the relationship
newNote.item = results[0];
}
Then whenever you have a particular Item object, you can access the corresponding note using
Note *myNote = myItem.note;
Furthermore, you can cascade the dot-notation, so get the value of attribute for the Note for myItem, use:
NSString *noteText = myItem.note.attribute;
EDIT
Your save method is very close: either set self.note = record before you save, or use newItem.note = record:
//in save method
// Create Entity
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Notes" inManagedObjectContext:self.managedObjectContext];
// Initialize New Record ie newNote
NSManagedObject *record = [[NSManagedObject alloc] initWithEntity:entity insertIntoManagedObjectContext:self.managedObjectContext];
// Populate Record
[record setValue:note forKey:#"note"];
[record setValue:localid forKey:#"localnid"];
// Save Record
NSError *error = nil;
if ([self.managedObjectContext save:&error]) {
// If note saved, save new item…
if (itemlength>1) {
Items *newItem = [NSEntityDescription insertNewObjectForEntityForName:#“Item” inManagedObjectContext:self.managedObjectContext];
newItem.item = item;
newItem.note = record;
if (![self.managedObjectContext save:&error]) {
NSLog(#"Error: %#", [error localizedDescription]);
}
}
}
To reach your goal you need to create a NSFetchRequest passing it the right NSPredicate.
The fetch request will be run against your Note entity. The predicate will allow you to specify that the Note object you want to retrieve is the one for that specific noteid.
So, if you have a 1-to-1 relationship between Item and Note, the NSPredicate should like the following:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"relationshipToItem.propertyForItemId == %#", yourItemId];
[request setPredicate:predicate];
Here I suppose you've a created a relationship between the two entities, otherwise you need to do it manually. Can you provide how your model looks like?

how to fetch data from two tables in core data objective-c

I'm new in use Core Data. I have a two entities CDContact and CDAddress. I have problem to fetch a second entity (CDAddress).
self.fetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"CDContact"];
[self.fetchRequest setSortDescriptors:#[[NSSortDescriptor sortDescriptorWithKey:#"firstName" ascending:YES]]];
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:self.fetchRequest managedObjectContext:[CoreDataManager sharedInstance].managedObjectContext sectionNameKeyPath:nil cacheName:nil];
[self.fetchedResultsController setDelegate:self];
[self fetchResults];
It works but I get only data from CDContact.
I try something like this (I found this on documentation):
NSManagedObjectContext *context = [CoreDataManager sharedInstance].managedObjectContext;
NSEntityDescription *contactEntity = [NSEntityDescription entityForName:#"CDContact" inManagedObjectContext:context];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
request.entity = contactEntity;
request.relationshipKeyPathsForPrefetching = [NSArray arrayWithObject:#"CDAddress"];
but in this version both entities doesn't work.
I'll be glad for any examples which help me resolve this problem.
As per comments, a fetch will only return results from a single entity. To access the second entity, you either need to run a second fetch, or to use the relationship on your CDContact objects. For example if the relationship is named address,
myCDContact.address
will give the CDAddress for the specified CDContact.
To expand on this, suppose your CDContact entity has attributes firstName and lastName, and a one-one relationship to CDAddress entitled address. Likewise suppose your CDAddress entity has attributes street, town, and zipCode, and the inverse relationship to CDContact entitled contact.
Then to create a new contact named "John Smith" living at "1 Main Street, AnyTown, 90210", you would have code something like this:
CDContact *myCDContact = (CDContact *)[NSEntityDescription insertNewObjectForEntityForName:#"CDContact" inManagedObjectContext:context];
myCDContact.firstName = #"John";
myCDContact.lastName = #"Smith";
CDAddress *myCDAddress = (CDAddress *)[NSEntityDescription insertNewObjectForEntityForName:#"CDAddress" inManagedObjectContext:context];
myCDAddress.street = #"1 Main Street";
myCDAddress.town = #"AnyTown";
myCDAddress.zipCode = #"90210";
// and lastly, to set the relationship between them...
// EITHER
myCDContact.address = myCDAddress;
// OR
myCDAddress.contact = myCDContact;
(Note the either/or: you only have to set the relationship "one-way", CoreData will set the inverse automatically). If you then save the context, and subsequently fetch the contacts, with something like this:
NSFetchRequest *fetch = [NSFetchRequest fetchRequestWithEntityName:#"CDContact"];
[fetch setSortDescriptors:#[[NSSortDescriptor sortDescriptorWithKey:#"firstName" ascending:YES]]];
NSError *error;
NSArray *results = [context executeFetchRequest:fetch error:&error];
then (assuming the fetch is successful: you should test for nil/error) the array will contain your contacts. Taking the first item in the array, you can then access its properties including the related CDAddress like this:
CDContact *myContact = (CDContact *)[results objectAtIndex:0];
NSLog(#"The contact is %# %#", myContact.firstName, myContact.lastName);
NSLog(#"who lives at %#, %#, %#", myContact.address.street, myContact.address.town, myContact.address.zipCode);
(All the above assumes you have created NSManagedObject subclasses for your entities. If you haven't, you should.)
The answer provided by pbasdf is perfect and very detailed. However, I would like to add this from the apple documentation.
To retrieve data using a managed object context, you create a fetch request. A fetch request is an object that specifies what data you want, for example, “all Employees,” or “all Employees in the Marketing department ordered by salary, highest to lowest.” A fetch request has three parts. Minimally it must specify the name of an entity (by implication, you can only fetch one type of entity at a time). It may also contain a predicate object that specifies conditions that objects must match and an array of sort descriptor objects that specifies the order in which the objects should appear...
Assuming the relationship between the entities CDContact and CDAddress is one-to-one, your CDContact interface file should look something like this:
#interface CDContact : NSManagedObject
#property (nonatomic,strong) NSString *firstName;
#property (nonatomic,strong) NSString *lastName;
#property (nonatomic,strong) CDAddress *address;
#end
This way, once you fetch a given CDContact object, you can access the related CDAddress object using the property "address". But if you need to access all addresses and all contacts, you need to perform two separate fetches.

Resources