is it possible to load data from .plist to core data object?
I have this code:
NSURL *url = [[NSBundle mainBundle] URLForResource:#"List" withExtension:#"plist"];
NSDictionary *plistContent = [NSDictionary dictionaryWithContentsOfURL:url];
NSArray *name = [plistContent objectForKey:#"Name"];
NSArray *surname = [plistContent objectForKey:#"Surname"];
NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
NSEntityDescription *entity = [[self.fetchedResultsController fetchRequest] entity];
NSManagedObject *newManagedObject = [NSEntityDescription insertNewObjectForEntityForName:[entity name] inManagedObjectContext:context];
for (NSString *aName in name) {
for (int i = 0; i < name.count; i++) {
[newManagedObject setValue:nameSt forKey:#"name"];
}
}
for (NSString *aSurname in content) {
for (int i = 0; i < content.count; i++) {
[newManagedObject setValue:contentSt forKey:#"surname"];
}
}
[self saveContext];
I have 3 strings with name and 3 string with surname, but in
newManagedObject load only 1 string from each array. Please help me to solve this problem
You create only one object, but you should create 3 (inside the loop). You should also only have 1 loop (not the 4 that you currently have...):
NSURL *url = [[NSBundle mainBundle] URLForResource:#"List" withExtension:#"plist"];
NSDictionary *plistContent = [NSDictionary dictionaryWithContentsOfURL:url];
NSArray *names = [plistContent objectForKey:#"Name"];
NSArray *surnames = [plistContent objectForKey:#"Surname"];
if (names.count == surnames.count) {
NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
NSEntityDescription *entity = [[self.fetchedResultsController fetchRequest] entity];
for (int i = 0; i < names.count; i++) {
NSString *name = names[i];
NSString *surname = surnames[i];
NSManagedObject *newManagedObject = [NSEntityDescription insertNewObjectForEntityForName:[entity name] inManagedObjectContext:context];
[newManagedObject setValue:name forKey:#"name"];
[newManagedObject setValue:surname forKey:#"surname"];
}
[self saveContext];
} else {
// error...
}
I don't think it's a good practice to store arrays of names and surnames in plist moreover separately... Anyway if this is really necessary, better to store array of persons with struct like your model
persons : [
{ name: Angelina,
surname: Jolie
},
{ name: Steven,
surname: Jobs
}
]
Then it will be easy to use method setValuesForKeysWithDictionary without unnecessary loops. If you are interesting, I can to show a more detailed example
EDIT: so if you have a lot of properties in you model, you can use setValuesForKeysWithDictionary method just once instead of setValue:forKey: for each key, but you need to overwrite last method in your model using mapping like this:
+ (NSDictionary *)mappings {
static NSDictionary *singleton = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
singleton = #{#"Name" : #"name",
#"Surname" : #"surname",
#"some_another_key" : #"your_property_name"};
});
return singleton;
}
- (void)setValue:(id)value forKey:(NSString *)key {
key = [[ModelClass mappings] objectForKey:key] ?: key;
if ([key isEqualToString:#"some_uniq_property"]) {
// ... do anything with value to transform it
// for example to get NSDate from NSString
}
[super setValue:value forKey:key];
}
- (void)setValue:(id)value forUndefinedKey:(NSString *)key {
// required method to avoid exception!
// you can intercept some key that you have forgotten to describe in mappings
// you can just retain it empty
NSLog(#"UndefinedKey: %#!!!", key);
}
Finally, your code will look something like this:
NSURL *url = [[NSBundle mainBundle] URLForResource:#"List" withExtension:#"plist"];
NSDictionary *plistContent = [NSDictionary dictionaryWithContentsOfURL:url];
NSArray *persons = [plistContent objectForKey:#"Persons"];
NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
NSEntityDescription *entity = [[self.fetchedResultsController fetchRequest] entity];
for (NSDictionary *person in persons) {
ModelClass *newManagedObject = [NSEntityDescription insertNewObjectForEntityForName:[entity name] inManagedObjectContext:context];
[newManagedObject setValuesForKeysWithDictionary:person];
}
[self saveContext];
For example: if you will want to rename some key or add new property to your model, you just will need to change mapping method instead going around all project and searching every place created\updated your managed objects
Related
I use NSEntityMigrationPolicy and createDestinationInstancesForSourceInstance method to change one attribute type from Data to String. However it creates new objects instead of replacing the existing one.
Here is my code:
#import "MessageTransformationPolicy.h"
#implementation MessageTransformationPolicy
- (BOOL)createDestinationInstancesForSourceInstance:(NSManagedObject *)sInstance
entityMapping:(NSEntityMapping *)mapping
manager:(NSMigrationManager *)manager
error:(NSError **)error
{
// Create a new object for the model context
NSManagedObject *newObject =
[NSEntityDescription insertNewObjectForEntityForName:[mapping destinationEntityName]
inManagedObjectContext:[manager destinationContext]];
// do our transfer of nsdate to nsstring
NSData *messageMetadata_dataType = [sInstance valueForKey:#"metadata"];
NSString *messageMetadata_stringType = [[NSString alloc] initWithData:messageMetadata_dataType encoding:NSUTF8StringEncoding];
// set the value for our new object
[newObject setValue:messageMetadata_stringType forKey:#"metadata"];
// do the coupling of old and new
[manager associateSourceInstance:sInstance withDestinationInstance:newObject forEntityMapping:mapping];
return YES;
}
#end
Where is the problem?
By changing the way of storing and saving all attributes of the target entity named Msg, the problem solved:
- (BOOL)createDestinationInstancesForSourceInstance:(NSManagedObject *)inSourceInstance
entityMapping:(NSEntityMapping *)inMapping
manager:(NSMigrationManager *)inManager
error:(NSError **)outError {
NSManagedObject *newObject;
NSEntityDescription *sourceInstanceEntity = [inSourceInstance entity];
// sure to have the right entity
if ( [[sourceInstanceEntity name] isEqualToString:#"Msg"] ) {
newObject = [NSEntityDescription insertNewObjectForEntityForName:#"Msg" inManagedObjectContext:[inManager destinationContext]];
// get the attributes
NSDictionary *keyValDict = [inSourceInstance committedValuesForKeys:nil];
NSArray *allKeys = [[[inSourceInstance entity] attributesByName] allKeys];
// loop over the attributes
for (NSString *key in allKeys) {
// Get key and value
id value = [keyValDict objectForKey:key];
if ( [key isEqualToString:#"metadata"] ) {
NSData *messageMetadata_dataType = [keyValDict valueForKey:#"metadata"];
NSString *messageMetadata_stringType = [[NSString alloc] initWithData:messageMetadata_dataType encoding:NSUTF8StringEncoding];
[newObject setValue:messageMetadata_stringType forKey:key];
} else {
[newObject setValue:value forKey:key];
}
}
[inManager associateSourceInstance:inSourceInstance withDestinationInstance:newObject forEntityMapping:inMapping];
}
return YES;
}
My program receives the JSON data from the Web service. Next, the program stores the data in the database using Core Data. If I call the save data after adding each entry, everything works, but very slowly. Keeping 200 entries takes more than one minute.
If I execute saving only once at the end – the program throw exception.
- (void) onLoadMessages:(NSObject*)object {
NSArray *messages = (NSArray*)object;
if (messages==nil) {
[self onError:#"Message array is null"];
return;
}
NSDate *date = [NSDate date];
long now = [date timeIntervalSince1970];
Boolean update = false;
for(int i=0; i<messages.count; i++) {
NSDictionary *m = messages[i];
Message *msg = [[Message alloc]initWithDictionary:m];
if ([self UpdateMessage:msg UpdateTime:now])
update = true;
}
if (update) {
NSError *error = nil;
// Error throw here
if (![self.managedObjectContext save:&error])
[self onError2:error];
}
}
- (Boolean) UpdateMessage:(Message*) msg UpdateTime:(long)now {
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Edit the entity name as appropriate.
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Messages" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
NSString *s = [NSString stringWithFormat:#"%ld", msg.id];
NSPredicate *pred = [NSPredicate predicateWithFormat:#"(id=%#)", s];
[fetchRequest setPredicate:pred];
NSError *error;
NSArray *object = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
Boolean result = false;
if (object==nil)
[self onError2:error];
else {
NSManagedObject *m;
if ([object count]==0) {
// Insert new message
m = [NSEntityDescription insertNewObjectForEntityForName:#"Messages"
inManagedObjectContext:self.managedObjectContext];
[m setValue:[NSNumber numberWithLong:msg.id] forKey:#"id"];
[m setValue:[NSNumber numberWithLong:msg.agancy.id] forKey:#"agancy"];
[m setValue:msg.header forKey:#"header"];
[m setValue:msg.keywords forKey:#"keywords"];
[m setValue:[NSNumber numberWithLong:msg.indate] forKey:#"indate"];
[m setValue:[NSNumber numberWithLong:now] forKey:#"updated"];
result = true;
} else {
// Update message
m = [object objectAtIndex:0];
[m setValue:[NSNumber numberWithLong:now] forKey:#"updated"];
}
// Save the context.
// Too long execution
/*NSError *error = nil;
if (![self.managedObjectContext save:&error])
[self onError2:error];*/
}
return result;
}
Help correct the behavior of the program.
With respect,
Alexander.
P.S.
Execution takes place in the main thread.
Field "Id" for table "Messages" indexed.
I solve the problem by adding privateObjectContext!
_privateObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
And replace managedObjectContext on privateObjectContext when updating/inserting data/
I'm trying to get to select or button one or two button the application will create an entity related to that neighborhood and thus show the following screens according to the selection. To avoid the creation of entities each time decided to use the NSUserDefaults and implementation is this:
- (void)carregarEntidadeLojaComId:(NSString *)identificadorLoja keyNSUserDefault:(NSString *)key {
if (![[NSUserDefaults standardUserDefaults] objectForKey:key]) {
NSManagedObjectContext *contexto = [self managedObjectContext];
NSArray *arrayLojas = [Utils carregarArrayPlist:identificadorLoja];
NSArray *atributosComuns = #[#"titulo", #"subtitulo", #"telefone", #"endereco"];
for (NSDictionary *dicionario in arrayLojas) {
loja = [NSEntityDescription insertNewObjectForEntityForName:#"Loja" inManagedObjectContext:contexto];
categoria = [NSEntityDescription insertNewObjectForEntityForName:#"Categoria" inManagedObjectContext:contexto];
quadra = [NSEntityDescription insertNewObjectForEntityForName:#"Quadra" inManagedObjectContext:contexto];
//Loop para atributos comuns
for (NSString *atributo in atributosComuns) {
[loja setValue:[dicionario objectForKey:atributo] forKey:atributo];
}
[categoria setValue:[dicionario objectForKey:#"categoria"] forKey:#"nome"];
[loja setValue:categoria forKey:#"categoria"];
[quadra setValue:[dicionario objectForKey:#"quadra"] forKey:#"nome"];
[loja setValue:quadra forKey:#"quadra"];
}
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:key];
[[NSUserDefaults standardUserDefaults] synchronize];
}
}
Turning the first time, the application identifies that there is no key and enters the if statement, therefore, create the entity. Already from the second time you use the application it will not enter the if statement, but he can not use the entity that has been created and so the screens that depend on them are not fulfilled. My question is how to force the application to use the entity that has been created the first time that the user clicked the application.
Is there really NSUserDefaults needed?
Why not just query entity from CoreData itself, like this:
- (void)carregarEntidadeLojaComId:(NSString *)identificadorLoja keyNSUserDefault:(NSString *)key {
NSManagedObjectContext *contexto = [self managedObjectContext];
NSFetchRequest *request = [NSFetchRequest new];
request.entity = [NSEntityDescription entityForName:#"Loja"
inManagedObjectContext:contexto];
request.predicate = [NSPredicate predicateWithFormat:#"identificador = %#", identificadorLoja];
NSError *error = nil;
NSArray *fetchedObjects = [contexto executeFetchRequest:request error:&error];
NSArray *lojas = nil;
if ((error == nil) && [fetchedObjects count] > 0)
lojas = fetchedObjects;
if (!lojas) {
NSArray *arrayLojas = [Utils carregarArrayPlist:identificadorLoja];
NSArray *atributosComuns = #[#"titulo", #"subtitulo", #"telefone", #"endereco"];
for (NSDictionary *dicionario in arrayLojas) {
loja = [NSEntityDescription insertNewObjectForEntityForName:#"Loja" inManagedObjectContext:contexto];
//Loop para atributos comuns
for (NSString *atributo in atributosComuns) {
[loja setValue:[dicionario objectForKey:atributo] forKey:atributo];
}
[loja setValue:[self categoriaWithNome:[dicionario objectForKey:#"categoria"]
inContexto:contexto]
forKey:#"categoria"];
[loja setValue:[self quadraWithNome:[dicionario objectForKey:#"quadra"]
inContexto:contexto]
forKey:#"quadra"];
[loja setValue:identificadorLoja
forKey:#"identificador"];
}
} else {
// do what you want with loja's, previously stored in CoreData
for (NSManagedObject *entity in lojas)
...
}
}
- (NSManagedObject *) categoriaWithNome:(NSObject *)nome inContexto:(NSManagedObjectContext *)contexto {
NSManagedObject *categoria = [NSEntityDescription insertNewObjectForEntityForName:#"Categoria" inManagedObjectContext:contexto];
[categoria setValue:nome forKey:#"nome"];
return categoria;
}
- (NSManagedObject *) quadraWithNome:(NSObject *)nome inContexto:(NSManagedObjectContext *)contexto {
NSManagedObject *quadra = [NSEntityDescription insertNewObjectForEntityForName:#"Quadra" inManagedObjectContext:contexto];
[quadra setValue:nome forKey:#"nome"];
return quadra;
}
Upd.
Added predicate to request and removed limit, as it seems like you have multiple loja's for each identificadorLoja.
I am creating objects in a core data database. My approach has been to create a category for each managed object and have a create method. This is working fine. However I have around 6 classes where the code is pretty much identical apart from the Class name.
Therefore I thought it would be better to have one class that passes in the name of the class being created and use:
Class classType = NSClassFromString(className);
so that I have access to that class type.
When calling;
NSDictionary *attributes = [[classType entity] attributesByName];
I get the following warning:
'Instance method 'entity' is being used on 'Class' which is not in the root class'.
Here is my code:
Header
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
#interface GenericObjectCreation : NSManagedObject
+ (NSMutableDictionary *)createNewObjectWithData:(NSDictionary *)data
className:(NSString *)className
inManagedObjectContext:(NSManagedObjectContext *)context;
#end
Implementation
#import "GenericObjectCreation.h"
#implementation GenericObjectCreation
+ (NSMutableDictionary *)createNewObjectWithData:(NSDictionary *)data
className:(NSString *)className
inManagedObjectContext:(NSManagedObjectContext *)context
{
Class classType = NSClassFromString(className);
classType = nil;
NSMutableDictionary *nexplanons = [NSMutableDictionary new];
for (id key in data) {
NSDictionary *element = [data objectForKey:key];
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:className];
request.predicate = [NSPredicate predicateWithFormat:#"organisationCode = %#", element[#"organisationCode"]];
NSError *error = nil;
NSArray *result = [context executeFetchRequest:request error:&error];
if ([result count] > 0) {
classType = result[0];
}
if (![result count]) {
classType = [NSEntityDescription insertNewObjectForEntityForName:className inManagedObjectContext:context];
}
// Loop through attributes on
NSDictionary *attributes = [[classType entity] attributesByName];
for (NSString *attribute in attributes) {
id value = [element objectForKey:attribute];
if (value == nil) {
continue;
}
[classType setValue:value forKey:attribute];
}
[nexplanons setObject:classType forKey:element[#"organisationCode"]];
}
[context save:nil];
return nexplanons;
}
#end
It seems like this should be possible and would remove a lot of code from my project which would be nice. Any help will be much appreciated.
As a side question, is this called reflection?
I'm simply trying to save a ManagedObjectContext but while I get no errors, the fetched request returns the object with none of the saved values. Consider this simple example. You have a single object, you change a property and save it. The object is there but the property is not saved. As you can see, I want only one object, and the fetch returns this one object. BTW, the code is in a simple class, not the app delegate or a view controller.
Here is the sample code:
MyAppDelegate* delegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext* context = delegate.managedObjectContext;
NSEntityDescription *myEntityDesc = [NSEntityDescription entityForName:#"MyEntity" inManagedObjectContext:context];
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
[request setEntity:myEntityDesc];
NSError *error = nil;
NSArray *array = [context executeFetchRequest:request error:&error];
MyEntity* myEntity;
if (array == nil || [array count] < 1)
{
myEntity = [NSEntityDescription insertNewObjectForEntityForName:#"MyEntity" inManagedObjectContext:context];
}
else
{
myEntity = [array objectAtIndex:0];
}
myEntity.BoolValue = [NSNumber numberWithBool:someBoolValue];
myEntity.ID = #"Some ID";
if ([context save:&error])
{
NSLog(#"no error");
}
else
{
NSLog([NSString stringWithFormat:#"found core data error: %#", [error localizedDescription]]);
}
Here's the code used to retrieve the values later:
MyAppDelegate* delegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext* context = delegate.managedObjectContext;
NSEntityDescription *MyEntityDesc = [NSEntityDescription entityForName:#"MyEntity" inManagedObjectContext:context];
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
[request setEntity:MyEntityDesc];
NSError *error = nil;
NSArray *array = [context executeFetchRequest:request error:&error];
MyEntity* myEntity;
if (array == nil || [array count] < 1)
{
//handle error
}
else
{
myEntity = [array objectAtIndex:0];
}
return [myEntity.BoolValue boolValue];
What does your NSManagedObject subclass look like? Since the fetch is working correctly (i.e. returning the entity), I suspect something is wrong in the subclass implementation.
You should declare a #property for each of the attributes on your data model. And in the implementation file, instead of using #synthesize you need to use #dynamic. Also make sure in your xcdatamodel that the entity has its class set, as well as the name.
#interface MyEntity : NSManagedObject
#property (nonatomic, strong) NSNumber * boolValue;
#end
#implementation MyEntity
#dynamic boolValue;
#end