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?
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;
}
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
I'm trying to add objects to an NSMutableArray but it keeps giving me this error.:
NSInternalInconsistencyException', reason: '-[__NSCFArray insertObject:atIndex:]: mutating method sent to immutable object
I have researched this problem, and I'm not doing anything wrong that past people have done, so I have no idea what's wrong. Here is my code:
Group.h
#property (strong, nonatomic) NSString *custom_desc;
#property (strong, nonatomic) NSMutableArray *attributes; //I define the array as mutable
Group.m
#import "Group.h"
#implementation Group
-(id)init
{
self = [super init];
if(self)
{
//do your object initialization here
self.attributes = [NSMutableArray array]; //I initialize the array to be a NSMutableArray
}
return self;
}
#end
GroupBuilder.m
#import "GroupBuilder.h"
#import "Group.h"
#implementation GroupBuilder
+ (NSArray *)groupsFromJSON:(NSData *)objectNotation error:(NSError **)error
{
NSError *localError = nil;
NSDictionary *parsedObject = [NSJSONSerialization JSONObjectWithData:objectNotation options:0 error:&localError];
if (localError != nil) {
*error = localError;
return nil;
}
NSMutableArray *groups = [[NSMutableArray alloc] init];
NSDictionary *results = [parsedObject objectForKey:#"result"];
NSArray *items = results[#"items" ];
for (NSDictionary *groupDic in items) {
Group *group = [[Group alloc] init];
for (NSString *key in groupDic) {
if ([group respondsToSelector:NSSelectorFromString(key)]) {
[group setValue:[groupDic valueForKey:key] forKey:key];
}
}
[groups addObject:group];
}
for(NSInteger i = 0; i < items.count; i++) {
//NSLog(#"%#", [[items objectAtIndex:i] objectForKey:#"attributes"]);
NSMutableArray *att = [[items objectAtIndex:i] objectForKey:#"attributes"]; //this returns a NSArray object understandable
Group *g = [groups objectAtIndex:i];
[g.attributes addObjectsFromArray:[att mutableCopy]]; //I use mutable copy here so that i'm adding objects from a NSMutableArray and not an NSArray
}
return groups;
}
#end
Use options:NSJSONReadingMutableContainers on your NSJSONSerialization call.
Then all the dictionaries and arrays it creates will be mutable.
According to the error message you are trying to insert an object into an instance of NSArray, not NSMutableArray.
I think it is here:
NSMutableArray *att = [[items objectAtIndex:i] objectForKey:#"attrib`enter code here`utes"]; //this returns a NSArray object understandable
Items is fetched from JSON and therefore not mutable. You can configure JSONSerialization in a way that it creates mutable objects, but how exactly I don't know out of the top of my head. Check the references on how to do that or make a mutable copy:
NSMutableArray *att = [[items objectAtIndex:i] objectForKey:#"attributes"] mutableCopy];
Next try, considering your replies to the first attempt:
#import "Group.h"
#implementation Group
-(NSMutableArray*)attributes
{
return [[super attributes] mutableCopy];
}
#end
My code here i am trying to add list of object in to my array form that array i trying to add it to code data attributes.
#import "ViewController.h"
#import "model.h"
#import "coredataManager.h"
#interface ViewController ()
{
NSMutableArray *entries;
}
#end
#implementation ViewController
- (void)viewDidLoad
{
entries = [[NSMutableArray alloc]init];
coredataManager *coreobj = [[coredataManager alloc]init];
model *obj = [[model alloc]initWithContents:#"1" alternateLink:#"1" DownloadURL:#"1"];
model *obj2 = [[model alloc]initWithContents:#"2" alternateLink:#"2" DownloadURL:#"2"];
model *obj3 = [[model alloc]initWithContents:#"3" alternateLink:#"3" DownloadURL:#"3"];
[entries addObject:obj];
[entries addObject:obj2];
[entries addObject:obj3];
NSLog(#"%#",entries);
[coreobj StoreValues:entries];
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
my NSObject Class
#import <Foundation/Foundation.h>
#interface model : NSObject
#property(copy)NSString *filename;
#property(copy)NSString *alternatelink;
#property(copy)NSString *downloadurl;
-(id)initWithContents:(NSString *)Fname alternateLink :(NSString *) ALink DownloadURL :(NSString *)DURL;
#end
NSObject Implementation File
#import "model.h"
#implementation model
#synthesize downloadurl,filename,alternatelink;
-(id)initWithContents:(NSString *)Fname alternateLink :(NSString *) ALink DownloadURL :(NSString *)DURL
{
if ((self = [super init])) {
downloadurl = [DURL copy];
filename = [Fname copy];
alternatelink = [ALink copy];
}
return self;
}
#end
Code to store in core data
-(void)StoreValues:(NSMutableArray *) sample
{
Sample *value = [NSEntityDescription insertNewObjectForEntityForName:#"Sample"
inManagedObjectContext:self.managedObjectContext];
for (int i=0;i<sample.count;i++)
{
model *obj=[sample objectAtIndex:i];
value.url = obj.downloadurl;
value.filename = obj.filename;
value.alternate = obj.alternatelink;
}
NSError *error;
if (![self.managedObjectContext save:&error]) {
NSLog(#"Whoops, couldn't save: %#", [error localizedDescription]);
}
}
But only the last value of the array is getting stored in the core data can any one guide me solve this issue
Thanks in advance.
Before you save, you rewrite the object. Do this instead:
Sample *value;
for (int i=0;i<sample.count;i++)
{
value = [NSEntityDescription insertNewObjectForEntityForName:#"Sample"
inManagedObjectContext:self.managedObjectContext];
model *obj=[sample objectAtIndex:i];
value.url = obj.downloadurl;
value.filename = obj.filename;
value.alternate = obj.alternatelink;
NSError *error;
if (![self.managedObjectContext save:&error]) {
NSLog(#"Whoops, couldn't save: %#", [error localizedDescription]);
}
}
That saves the object after every iteration and doesn't rewrite it. Then starts anew.
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