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;
}
Related
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.
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 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 am trying to load a csv file in core data when the application is ran for the first time. On another post on stackoverflow found here (What is the fastest way to load a large CSV file into core data), I found out how to do that.
I am using the same code form the provided function: populateDB, in my controller and calling the function if the data has never been loaded before (first run). However, xcode is giving me an error:
No visible #interface for ...Controller declares the selector persistentStoreCoordinator.
The function is:
-(void)populateDB{
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
NSManagedObjectContext *context;
if (coordinator != nil) {
context = [[NSManagedObjectContext alloc] init];
[context setPersistentStoreCoordinator:coordinator];
}
NSString *filePath = [[NSBundle mainBundle] pathForResource:#"input" ofType:#"txt"];
if (filePath) {
NSString * myText = [[NSString alloc]
initWithContentsOfFile:filePath
encoding:NSUTF8StringEncoding
error:nil];
if (myText) {
__block int count = 0;
[myText enumerateLinesUsingBlock:^(NSString * line, BOOL * stop) {
line=[line stringByReplacingOccurrencesOfString:#"\t" withString:#" "];
NSArray *lineComponents=[line componentsSeparatedByString:#" "];
if(lineComponents){
if([lineComponents count]==3){
float f=[[lineComponents objectAtIndex:0] floatValue];
NSNumber *number=[NSNumber numberWithFloat:f];
NSString *string1=[lineComponents objectAtIndex:1];
NSString *string2=[lineComponents objectAtIndex:2];
NSManagedObject *object=[NSEntityDescription insertNewObjectForEntityForName:#"Bigram" inManagedObjectContext:context];
[object setValue:number forKey:#"number"];
[object setValue:string1 forKey:#"string1"];
[object setValue:string2 forKey:#"string2"];
NSError *error;
count++;
if(count>=1000){
if (![context save:&error]) {
NSLog(#"Whoops, couldn't save: %#", [error localizedDescription]);
}
count=0;
}
}
}
}];
NSLog(#"done importing");
NSError *error;
if (![context save:&error]) {
NSLog(#"Whoops, couldn't save: %#", [error localizedDescription]);
}
}
}
}
I am picking up iOS again after 3 years of absence and I have never dived into this part of the SDK before. I would greatly appreciate any help with this issue...
The code below shows you an example to load csv file in Core Data using the class CSVParser.hof Matt Gallagher and supposing an entity MyEntity
// CSV File input.csv and not input.txt separate by space #" "
CSVParser *csvParser = [[CSVParser alloc] initWithContentOfFile:[NSBundle mainBundle] pathForResource:#"input" ofType:#"csv" separator:#" "];
// Array with all your data from CSV
NSArray *data = [csvParser parseFile];
// Your entity from Core Data
MyEntity *myEntity = nil;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
for (NSDictionary *dic in data)
{
fetchRequest.entity = [NSEntityDescription entityForName:#"MyEntity" inManagedObjectContext:context];
if (!entity)
entity = [NSEntityDescription insertNewObjectForEntityForName:#"MyEntity"
inManagedObjectContext:context];
[entity setValue:number forKey:#"number"];
[entity setValue:string1 forKey:#"string1"];
[entity setValue:string2 forKey:#"string2"];
}
// Save the context
[managedObjectContext save:nil];
CVSParser.h using ARC :
//
// CSVParser.h
// CSVImporter
//
// Created by Matt Gallagher on 2009/11/30.
// Copyright 2009 Matt Gallagher. All rights reserved.
//
// Permission is given to use this source code file, free of charge, in any
// project, commercial or otherwise, entirely at your risk, with the condition
// that any redistribution (in part or whole) of source code must retain
// this copyright and permission notice. Attribution in compiled projects is
// appreciated but not required.
//
// Source : http://cocoawithlove.com/2009/11/writing-parser-using-nsscanner-csv.html
#interface CSVParser : NSObject
{
NSString *csvString;
NSString *separator;
NSScanner *scanner;
BOOL hasHeader;
NSMutableArray *fieldNames;
id receiver;
SEL receiverSelector;
NSCharacterSet *endTextCharacterSet;
BOOL separatorIsSingleChar;
}
- (id)initWithString:(NSString *)aCSVString
separator:(NSString *)aSeparatorString;
- (id)initWithContentOfFile:(NSString *)aPath
separator:(NSString *)aSeparatorString;
- (id)initWithString:(NSString *)aCSVString
separator:(NSString *)aSeparatorString
hasHeader:(BOOL)header
fieldNames:(NSArray *)names;
- (id)initWithContentOfFile:(NSString *)aPath
separator:(NSString *)aSeparatorString
hasHeader:(BOOL)header
fieldNames:(NSArray *)names;
- (NSArray *)arrayOfParsedRows;
- (void)parseRowsForReceiver:(id)aReceiver selector:(SEL)aSelector;
- (NSArray *)parseFile;
- (NSMutableArray *)parseHeader;
- (NSDictionary *)parseRecord;
- (NSString *)parseName;
- (NSString *)parseField;
- (NSString *)parseEscaped;
- (NSString *)parseNonEscaped;
- (NSString *)parseDoubleQuote;
- (NSString *)parseSeparator;
- (NSString *)parseLineSeparator;
- (NSString *)parseTwoDoubleQuotes;
- (NSString *)parseTextData;
#end
CVSParser.m using ARC :
//
// CSVParser.m
// CSVImporter
//
// Created by Matt Gallagher on 2009/11/30.
// Copyright 2009 Matt Gallagher. All rights reserved.
//
// Permission is given to use this source code file, free of charge, in any
// project, commercial or otherwise, entirely at your risk, with the condition
// that any redistribution (in part or whole) of source code must retain
// this copyright and permission notice. Attribution in compiled projects is
// appreciated but not required.
//
#import "CSVParser.h"
#implementation CSVParser
//
// initWithString:separator:hasHeader:fieldNames:
//
// Parameters:
// aCSVString - the string that will be parsed
// aSeparatorString - the separator (normally "," or "\t")
// header - if YES, treats the first row as a list of field names
// names - a list of field names (will have no effect if header is YES)
//
// returns the initialized object (nil on failure)
//
- (id)initWithString:(NSString *)aCSVString
separator:(NSString *)aSeparatorString
hasHeader:(BOOL)header
fieldNames:(NSArray *)names
{
self = [super init];
if (self)
{
csvString = [aCSVString retain];
separator = [aSeparatorString retain];
NSAssert([separator length] > 0 &&
[separator rangeOfString:#"\""].location == NSNotFound &&
[separator rangeOfCharacterFromSet:[NSCharacterSet newlineCharacterSet]].location == NSNotFound,
#"CSV separator string must not be empty and must not contain the double quote character or newline characters.");
NSMutableCharacterSet *endTextMutableCharacterSet =
[[NSCharacterSet newlineCharacterSet] mutableCopy];
[endTextMutableCharacterSet addCharactersInString:#"\""];
[endTextMutableCharacterSet addCharactersInString:[separator substringToIndex:1]];
endTextCharacterSet = endTextMutableCharacterSet;
if ([separator length] == 1)
{
separatorIsSingleChar = YES;
}
hasHeader = header;
fieldNames = [names mutableCopy];
}
return self;
}
- (id)initWithString:(NSString *)aCSVString
separator:(NSString *)aSeparatorString
{
return [self initWithString:aCSVString
separator:aSeparatorString
hasHeader:YES
fieldNames:nil];
}
- (id)initWithContentOfFile:(NSString *)aPath
separator:(NSString *)aSeparatorString
{
return [self initWithString:[NSString stringWithContentsOfFile:aPath
encoding:NSUTF8StringEncoding
error:nil]
separator:aSeparatorString];
}
- (id)initWithContentOfFile:(NSString *)aPath
separator:(NSString *)aSeparatorString
hasHeader:(BOOL)header
fieldNames:(NSArray *)names
{
return [self initWithString:[NSString stringWithContentsOfFile:aPath
encoding:NSUTF8StringEncoding
error:nil]
separator:aSeparatorString
hasHeader:header
fieldNames:names];
}
//
// dealloc
//
// Releases instance memory.
//
- (void)dealloc
{
[csvString release];
[separator release];
[fieldNames release];
[endTextCharacterSet release];
[super dealloc];
}
//
// arrayOfParsedRows
//
// Performs a parsing of the csvString, returning the entire result.
//
// returns the array of all parsed row records
//
- (NSArray *)arrayOfParsedRows
{
scanner = [[NSScanner alloc] initWithString:csvString];
[scanner setCharactersToBeSkipped:[[[NSCharacterSet alloc] init] autorelease]];
NSArray *result = [self parseFile];
[scanner release];
scanner = nil;
return result;
}
//
// parseRowsForReceiver:selector:
//
// Performs a parsing of the csvString, sending the entries, 1 row at a time,
// to the receiver.
//
// Parameters:
// aReceiver - the target that will receive each row as it is parsed
// aSelector - the selector that will receive each row as it is parsed
// (should be a method that takes a single NSDictionary argument)
//
- (void)parseRowsForReceiver:(id)aReceiver selector:(SEL)aSelector
{
scanner = [[NSScanner alloc] initWithString:csvString];
[scanner setCharactersToBeSkipped:[[[NSCharacterSet alloc] init] autorelease]];
receiver = [aReceiver retain];
receiverSelector = aSelector;
[self parseFile];
[scanner release];
scanner = nil;
[receiver release];
receiver = nil;
}
//
// parseFile
//
// Attempts to parse a file from the current scan location.
//
// returns the parsed results if successful and receiver is nil, otherwise
// returns nil when done or on failure.
//
- (NSArray *)parseFile
{
scanner = [[NSScanner alloc] initWithString:csvString];
[scanner setCharactersToBeSkipped:[[[NSCharacterSet alloc] init] autorelease]];
if (hasHeader)
{
if (fieldNames)
{
[fieldNames release];
}
fieldNames = [[self parseHeader] retain];
if (!fieldNames || ![self parseLineSeparator])
{
return nil;
}
}
NSMutableArray *records = nil;
if (!receiver)
{
records = [NSMutableArray array];
}
NSDictionary *record = [[self parseRecord] retain];
if (!record)
{
return nil;
}
while (record)
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
if (receiver)
{
[receiver performSelector:receiverSelector withObject:record];
}
else
{
[records addObject:record];
}
[record release];
if (![self parseLineSeparator])
{
break;
}
record = [[self parseRecord] retain];
[pool drain];
}
[scanner release];
scanner = nil;
return records;
}
//
// parseHeader
//
// Attempts to parse a header row from the current scan location.
//
// returns the array of parsed field names or nil on parse failure.
//
- (NSMutableArray *)parseHeader
{
NSString *name = [self parseName];
if (!name)
{
return nil;
}
NSMutableArray *names = [NSMutableArray array];
while (name)
{
[names addObject:name];
if (![self parseSeparator])
{
break;
}
name = [self parseName];
}
return names;
}
//
// parseRecord
//
// Attempts to parse a record from the current scan location. The record
// dictionary will use the fieldNames as keys, or FIELD_X for each column
// X-1 if no fieldName exists for a given column.
//
// returns the parsed record as a dictionary, or nil on failure.
//
- (NSDictionary *)parseRecord
{
//
// Special case: return nil if the line is blank. Without this special case,
// it would parse as a single blank field.
//
if ([self parseLineSeparator] || [scanner isAtEnd])
{
return nil;
}
NSString *field = [self parseField];
if (!field)
{
return nil;
}
NSInteger fieldNamesCount = [fieldNames count];
NSInteger fieldCount = 0;
NSMutableDictionary *record =
[NSMutableDictionary dictionaryWithCapacity:[fieldNames count]];
while (field)
{
NSString *fieldName;
if (fieldNamesCount > fieldCount)
{
fieldName = [fieldNames objectAtIndex:fieldCount];
}
else
{
fieldName = [NSString stringWithFormat:#"FIELD_%d", fieldCount + 1];
[fieldNames addObject:fieldName];
fieldNamesCount++;
}
[record setObject:field forKey:fieldName];
fieldCount++;
if (![self parseSeparator])
{
break;
}
field = [self parseField];
}
return record;
}
//
// parseName
//
// Attempts to parse a name from the current scan location.
//
// returns the name or nil.
//
- (NSString *)parseName
{
return [self parseField];
}
//
// parseField
//
// Attempts to parse a field from the current scan location.
//
// returns the field or nil
//
- (NSString *)parseField
{
NSString *escapedString = [self parseEscaped];
if (escapedString)
{
return escapedString;
}
NSString *nonEscapedString = [self parseNonEscaped];
if (nonEscapedString)
{
return nonEscapedString;
}
//
// Special case: if the current location is immediately
// followed by a separator, then the field is a valid, empty string.
//
NSInteger currentLocation = [scanner scanLocation];
if ([self parseSeparator] || [self parseLineSeparator] || [scanner isAtEnd])
{
[scanner setScanLocation:currentLocation];
return #"";
}
return nil;
}
//
// parseEscaped
//
// Attempts to parse an escaped field value from the current scan location.
//
// returns the field value or nil.
//
- (NSString *)parseEscaped
{
if (![self parseDoubleQuote])
{
return nil;
}
NSString *accumulatedData = [NSString string];
while (YES)
{
NSString *fragment = [self parseTextData];
if (!fragment)
{
fragment = [self parseSeparator];
if (!fragment)
{
fragment = [self parseLineSeparator];
if (!fragment)
{
if ([self parseTwoDoubleQuotes])
{
fragment = #"\"";
}
else
{
break;
}
}
}
}
accumulatedData = [accumulatedData stringByAppendingString:fragment];
}
if (![self parseDoubleQuote])
{
return nil;
}
return accumulatedData;
}
//
// parseNonEscaped
//
// Attempts to parse a non-escaped field value from the current scan location.
//
// returns the field value or nil.
//
- (NSString *)parseNonEscaped
{
return [self parseTextData];
}
//
// parseTwoDoubleQuotes
//
// Attempts to parse two double quotes from the current scan location.
//
// returns a string containing two double quotes or nil.
//
- (NSString *)parseTwoDoubleQuotes
{
if ([scanner scanString:#"\"\"" intoString:NULL])
{
return #"\"\"";
}
return nil;
}
//
// parseDoubleQuote
//
// Attempts to parse a double quote from the current scan location.
//
// returns #"\"" or nil.
//
- (NSString *)parseDoubleQuote
{
if ([scanner scanString:#"\"" intoString:NULL])
{
return #"\"";
}
return nil;
}
//
// parseSeparator
//
// Attempts to parse the separator string from the current scan location.
//
// returns the separator string or nil.
//
- (NSString *)parseSeparator
{
if ([scanner scanString:separator intoString:NULL])
{
return separator;
}
return nil;
}
//
// parseLineSeparator
//
// Attempts to parse newline characters from the current scan location.
//
// returns a string containing one or more newline characters or nil.
//
- (NSString *)parseLineSeparator
{
NSString *matchedNewlines = nil;
[scanner
scanCharactersFromSet:[NSCharacterSet newlineCharacterSet]
intoString:&matchedNewlines];
return matchedNewlines;
}
//
// parseTextData
//
// Attempts to parse text data from the current scan location.
//
// returns a non-zero length string or nil.
//
- (NSString *)parseTextData
{
NSString *accumulatedData = [NSString string];
while (YES)
{
NSString *fragment;
if ([scanner scanUpToCharactersFromSet:endTextCharacterSet intoString:&fragment])
{
accumulatedData = [accumulatedData stringByAppendingString:fragment];
}
//
// If the separator is just a single character (common case) then
// we know we've reached the end of parseable text
//
if (separatorIsSingleChar)
{
break;
}
//
// Otherwise, we need to consider the case where the first character
// of the separator is matched but we don't have the full separator.
//
NSUInteger location = [scanner scanLocation];
NSString *firstCharOfSeparator;
if ([scanner scanString:[separator substringToIndex:1] intoString:&firstCharOfSeparator])
{
if ([scanner scanString:[separator substringFromIndex:1] intoString:NULL])
{
[scanner setScanLocation:location];
break;
}
//
// We have the first char of the separator but not the whole
// separator, so just append the char and continue
//
accumulatedData = [accumulatedData stringByAppendingString:firstCharOfSeparator];
continue;
}
else
{
break;
}
}
if ([accumulatedData length] > 0)
{
return accumulatedData;
}
return nil;
}
#end
Thanks for everyone's assistance. I found the answer on stackoverflow, a 2-3 years old forum (Adding Core Data to existing iPhone project)...
So the issue it seems is that when I first created the project I didn't request using core data and only did that later on. Following the post I posted above:
Add the following to supporting files/projectName-Prefix.pch
#import <CoreData/CoreData.h>
Once I did, the persistenceCoordinator error disappeared...
WOW, big lesson learned there...
-(void)populateDB {
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
NSManagedObjectContext *context;
[....]
}
The Problem might be [self persistentStoreCoordinator]... Do you have a function called "persistentStoreCoordinator" ? If not, you have to write the function.
[EDIT]
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
#synchronized (self)
{
if (__persistentStoreCoordinator != nil)
return __persistentStoreCoordinator;
NSString *defaultStorePath = [[NSBundle mainBundle] pathForResource:#"myProject" ofType:#"sqlite"];
NSString *storePath = [[[self applicationDocumentsDirectory] path] stringByAppendingPathComponent: #"myProject.sqlite"];
NSError *error;
if (![[NSFileManager defaultManager] fileExistsAtPath:storePath])
{
if ([[NSFileManager defaultManager] copyItemAtPath:defaultStorePath toPath:storePath error:&error])
NSLog(#"Copied starting data to %#", storePath);
else
NSLog(#"Error copying default DB to %# (%#)", storePath, error);
}
NSURL *storeURL = [NSURL fileURLWithPath:storePath];
__persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
if (![__persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error])
{
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return __persistentStoreCoordinator;
}
}
Is this Code in another File ? Maybe you forgot to import the .h-file, where persistentStoreCoordinator is declared in.
My old core data model has an NSDate field, which I would like to change to a NSNumber. I read the Apple documentation and several similar questions on SO and other blogs (see references at end of question)
But no matter what I do, I keep getting the same error:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Mismatch between mapping and source/destination models'
I only have 2 versions of the model, and I have verified time and again that the source and destination models are correct.
I even discarded all my changes and recreated a new model, mappings and entities (NSManagedObject subclasses). I've been stuck on this for almost 2 days now, and have no clue anymore as to what I'm doing. Any pointers on what I'm doing wrong will be greatly appreciated.
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
if (_persistentStoreCoordinator != nil) {
return _persistentStoreCoordinator;
}
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"Old.sqlite"];
NSError *error = nil;
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
NSString *sourceStoreType = NSSQLiteStoreType;
NSURL *sourceStoreURL = storeURL;
NSURL *destinationStoreURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"New.sqlite"];
NSString *destinationStoreType = NSSQLiteStoreType;
NSDictionary *destinationStoreOptions = nil;
NSDictionary *sourceMetadata =
[NSPersistentStoreCoordinator metadataForPersistentStoreOfType:sourceStoreType
URL:sourceStoreURL
error:&error];
if (sourceMetadata == nil) {
NSLog(#"source metadata is nil");
}
NSManagedObjectModel *destinationModel = [_persistentStoreCoordinator managedObjectModel];
BOOL pscCompatibile = [destinationModel
isConfiguration:nil
compatibleWithStoreMetadata:sourceMetadata];
if (pscCompatibile) {
// no need to migrate
NSLog(#"is compatible");
} else {
NSLog(#"is not compatible");
NSManagedObjectModel *sourceModel =
[NSManagedObjectModel mergedModelFromBundles:nil
forStoreMetadata:sourceMetadata];
if (sourceModel != nil) {
NSLog(#"source model is not nil");
NSMigrationManager *migrationManager =
[[NSMigrationManager alloc] initWithSourceModel:sourceModel
destinationModel:destinationModel];
NSURL *fileURL = [[NSBundle mainBundle] URLForResource:#"MyMigrationMapping" withExtension:#"cdm"];
NSMappingModel *mappingModel = [[NSMappingModel alloc] initWithContentsOfURL:fileURL];
NSArray *newEntityMappings = [NSArray arrayWithArray:mappingModel.entityMappings];
for (NSEntityMapping *entityMapping in newEntityMappings) {
entityMapping.entityMigrationPolicyClassName = NSStringFromClass([ConvertDateToNumberTransformationPolicy class]);
}
mappingModel.entityMappings = newEntityMappings;
BOOL ok = [migrationManager migrateStoreFromURL:sourceStoreURL
type:sourceStoreType
options:nil
withMappingModel:mappingModel
toDestinationURL:destinationStoreURL
destinationType:destinationStoreType
destinationOptions:nil
error:&error];
if (ok) {
storeURL = destinationStoreURL;
}
} else {
NSLog(#"e nil source model");
}
}
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption,
nil];
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return _persistentStoreCoordinator;
}
My custom NSEntityMigration class:
- (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]];
NSArray *arrayOfKeys = #[#"startDate", #"endDate", #"creationTime", #"timeStamp"];
for (NSString *key in arrayOfKeys) {
// do our transfer of NSDate to NSNumber
NSDate *date = [sInstance valueForKey:key];
NSLog(#"Key: %#, value: %#", key, [date description]);
// set the value for our new object
[newObject setValue:[NSNumber numberWithDouble:[date timeIntervalSince1970]] forKey:key];
}
// do the coupling of old and new
[manager associateSourceInstance:sInstance withDestinationInstance:newObject forEntityMapping:mapping];
return YES;
}
Some references:
Example or explanation of Core Data Migration with multiple passes?
Core Data - Default Migration ( Manual )
http://www.preenandprune.com/cocoamondo/?p=468
http://www.timisted.net/blog/archive/core-data-migration/
I admit I don't understand the cause of the error. In my migration I have one policy per entity and I am checking for the entity before using it. Not sure if this extra if will help you:
- (BOOL)createDestinationInstancesForSourceInstance:(NSManagedObject *)sInstance
entityMapping:(NSEntityMapping *)mapping
manager:(NSMigrationManager *)manager
error:(NSError **)error {
NSEntityDescription *sourceInstanceEntity = [sInstance entity];
if ([[sInstance name] isEqualToString:#"<-name-of-entity>"] ) {
newObject = [NSEntityDescription insertNewObjectForEntityForName:#"<-name-of-entity>"
inManagedObjectContext:[manager destinationContext]];
NSArray *arrayOfKeys = #[#"startDate", #"endDate", #"creationTime", #"timeStamp"];
for (NSString *key in arrayOfKeys) {
// do our transfer of NSDate to NSNumber
NSDate *date = [sInstance valueForKey:key];
NSLog(#"Key: %#, value: %#", key, [date description]);
// set the value for our new object
[newObject setValue:[NSNumber numberWithDouble:[date timeIntervalSince1970]] forKey:key];
}
}
// do the coupling of old and new
[manager associateSourceInstance:sInstance withDestinationInstance:newObject forEntityMapping:mapping];
return YES;
}
Everything you are doing is way more complicated than it has to be. You can do all of this without migrating you database at all. You can add another property to your subclass that implements it:
///in your .h
#property(nonatomic, copy) NSNumber* startDateNumber
/// in you .m
-(NSNumber*) startDateNumber{
if (self.startDate) {
return #(self.startDate.timeIntervalSince1970);
}
return nil;
}
-(void)setStartDateNumber:(NSNumber*)startDateNumber{
if(startDateNumber){
self.startDate =[NSDate dateWithTimeIntervalSince1970:startDateNumber.doubleValue];
}else{
self.startDate = nil;
}
}
It is a little annoying to have duplicate properties (startDate and startDateNumber) but it is so much simpler and doesn't have any of the migration issues.