I am working on an application which stores and retrieves data from sqlite db, using core data. For this I have created a separate class which acts like a data link layer - LocalDBController
Below is the implementation of one of its methods- selectAddressWithAddressId:
- (NSDictionary *)selectAddressWithAddressId:(NSString *)addressId
{
NSDictionary *dictToReturn = nil;
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"address_id == %#",addressId];
NSEntityDescription *entityDescription = [NSEntityDescription entityForName:#"Address" inManagedObjectContext:self.moc]; // returning nil when invoked from test case class
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entityDescription];
[request setPredicate:predicate];
NSError *err = nil;
NSArray *array = [self.moc executeFetchRequest:request error:&err];
// some more code...
return dictToReturn;
}
Now I am trying to implement a test case class for it (SenTestCase class).
I have written below init method in LocalDBController class, so that it uses the default persistent store if value of environment variable is 'Run' and uses in-memory persistent store if value of environment variable is 'Test':
- (id)init
{
if (self = [super init]) {
// initializing moc based on if run setting is used or test is used
if ([[[[NSProcessInfo processInfo] environment] objectForKey:#"TARGET"] isEqualToString:#"TEST"]) {
NSManagedObjectModel *mom = [NSManagedObjectModel mergedModelFromBundles:nil];
NSPersistentStoreCoordinator *psc = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:mom];
[psc addPersistentStoreWithType:NSInMemoryStoreType configuration:nil URL:nil options:nil error:NULL];
self.moc = [[NSManagedObjectContext alloc] init];
self.moc.persistentStoreCoordinator = psc;
}
else
{
self.moc = [(AppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
}
}
return self;
}
In my test class I am trying to invoke below method:
STAssertNotNil([self.localDBController selectAddressWithAddressId:#"123"], #"No data found");
Problem is-
In this case, value of entityDescription obtained in selectAddressWithAddressId: method is nil, though the value of self.moc is not nil. So it
is throwing this exception msg in console: raised
executeFetchRequest:error: A fetch request must have an entity..
If I execute the above method from the class which is not included in my test case bundle, say appDelegate, it works fine.
Can anyone suggest me if I am doing anything wrong in it?
Related
Problem: Fetching a managed object using a background thread does not lazy load the NSManaged object relationship correctly when the NSManaged object that is related has a custom setter. Doing fetch on main thread with main concurrency type works without a problem. Why is this?
Work Around: If I create a custom getter on the relationship object and check for nil, I can force the NSManaged object to load by calling other variables that don't have custom setter methods.
Background
The core data layout is pretty simple. I have a Game managed object and a Turn managed object. The turn object is a one to one relationship with the game object. I always fetch the game object in order to access the turn object. TurnImp and GameImp are implementation classes that inherit from the Game and Turn object so I don't put getter/setter methods in auto generated code.
Code
The Fetch
//
//Stick command on background
//
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^ {
//
//Load Game
//
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
CoreDataHelper *coreDataHelper = appDelegate.coreDataHelper;
NSManagedObjectContext *childMOC = [coreDataHelper createChildManagedObjectContext];
//the request
NSFetchRequest *fetchRequest = [NSFetchRequest new];
//the object entity we want
NSEntityDescription *entity = [NSEntityDescription entityForName:GAMEIMP_GAME inManagedObjectContext:childMOC];
[fetchRequest setEntity:entity];
//the predicate rules, the what
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"gameId == %#", #"1404110671234567"];
[fetchRequest setPredicate:predicate];
//the sorting rules
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc]initWithKey:GAMEIMP_OBJECT_ID ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc]initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
//Fetch results
NSFetchedResultsController *resultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:childMOC sectionNameKeyPath:nil cacheName:nil];
NSError *error;
BOOL success = [resultsController performFetch:&error];
GameImp *game;
if (success) {
game = [resultsController.fetchedObjects objectAtIndex:0];
} else {
NSLog(#"Unable to get game. Error: %#", error);
}
TurnImp *turnImp = game.turn;
//Issue is here!!! Should be 3, instead 0 because lastRoundReward is nil.
int lastRoundReward = [turnImp.lastRoundReward intValue];
//Work around, call custom getter method. Now 3 is returned.
lastRoundReward = [turnImp getLastRoundReward];
}
This childMOC creation
-(NSManagedObjectContext*) createChildManagedObjectContext {
NSManagedObjectContext *childMOC = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
childMOC.parentContext = self.mainManagedObjectContext;
return childMOC;
}
TurnImp Header
#interface TurnImp : Turn
#property(atomic) BOOL isValid;
- (void) setLastRoundReward: (int) lastRoundReward;
- (int) getLastRoundReward;
#end
TurnImp M
#implementation TurnImp
#synthesize isValid;
#synthesize lastRoundReward = _lastRoundReward;
/**
* Set the last round reward
* #param -
* #return -
*/
- (void) setLastRoundReward: (int) lastRoundReward {
_lastRoundReward = [NSNumber numberWithInt:lastRoundReward];
}
/**
* Get the int value of lastRoundReward
*/
- (int) getLastRoundReward {
//Note - HACK! Lazy loading not working, try another member
if (self.lastRoundReward == nil) {
//Force load
NSString *objectId = self.objectId;
}
return [self.lastRoundReward intValue];
}
Change childMoc to mainMoc and it works. MainMoc Code
//create the main MOC
_mainManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
More After Fixed Concurrency issue
[childMOC performBlock:^{
// Execute the fetch on the childMOC and do your other work.
NSError *error;
NSArray *results = [childMOC executeFetchRequest:fetchRequest error:&error];
if (results == nil) {
// Handle error
} else if (results.count == 1) {
GameImp *game = [results firstObject];
TurnImp *turnImp = game.turn;
//Issue is here!!! Should be 3, instead 0 because lastRoundReward is nil.
int lastRoundReward = [turnImp.lastRoundReward intValue];
//Work around, call variable objectId (not same as ObjectId)
NSString *objectId = turnImp.objectId;
//not it's 3...
lastRoundReward = [turnImp.lastRoundReward intValue];
}
}];
Work Around
I removed the following from TurnImp and it works as expected with the relationships.
#synthesize lastRoundReward = _lastRoundReward;
First, I have to confess that I have no idea what your problem statement means - what is lazy loading of a relationship supposed to do anyway?
However, a quick glance at your code reveals that you are creating a MOC with NSPrivateQueueConcurrencyType yet you are not properly wrapping its use inside an appropriate performBlock invocation.
When you clearly violate the Core Data Concurrency guidelines, you are playing in dangerous waters and will get undefined behavior.
Also, why create an instance of NSFetchedResultsController just to perform a fetch? That's overkill. Simply use a fetch request. Like so...
[childMOC performBlock:^{
// Execute the fetch on the childMOC and do your other work.
NSError *error;
NSArray *results = [childMOC executeFetchRequest:fetchRequest error:&error];
if (result == nil) {
// Handle error
} else if (results.count == 1) {
GameImp *game = [results firstObject];
TurnImp *turnImp = game.turn;
int lastRoundReward = [turn2.lastRoundReward intValue];
}
}];
I'm using Magical Record to manage Core Data in my app which uses Kinvey. On my NSManagedObject I've implemented the following method as a requirement for Kinvey's SDK to work with Core Data.
+ (id)kinveyDesignatedInitializer:(NSDictionary *)jsonDocument
{
NSString* existingID = jsonDocument[KCSEntityKeyId];
Task* obj = nil;
NSManagedObjectContext* context = [NSManagedObjectContext MR_defaultContext];
NSEntityDescription* entity = [NSEntityDescription entityForName:#"Task" inManagedObjectContext:context];
if (existingID) {
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"kinvey_id = %#", existingID];
[request setPredicate:predicate];
NSArray* results = [context executeFetchRequest:request error:NULL];
if (results != nil && results.count > 0) {
obj = results[0];
}
}
if (obj == nil) {
//fall back to creating a new if one if there is an error, or if it is new
obj = [[self alloc] initWithEntity:entity insertIntoManagedObjectContext:context];
}
return obj;
}
However I'm not sure if this is the correct way of obtaining the context for the NSManagedObject.
NSManagedObjectContext* context = [NSManagedObjectContext MR_defaultContext];
Also I'm extremely confused on handling my NSManagedObjectContexts and NSManagedObjects
In one of my current apps I've set my structure as this:
Main App View Controller
- self.editingContext = [NSManagedObjectContext MR_defaultContext];
Detail Object Views
- self.editingContext = [NSManagedObjectContext MR_contextForCurrentThread];
- self.currentObject = [self.editingContext objectWithID:objectID]; (objectID passed from MAVC)
I'm getting reports of the app crashing, so something's obviously not right.
For the new app I was thinking to have the following structure but I'm not sure if it's okay to pass managedobjects and contexts across view controllers.
Main App View Controller
- self.editingContext = [NSManagedObjectContext MR_defaultContext];
- detailVC.currentObject = managedObject;
Detail Object Views
- self.editingContext = self.currentObject.managedObjectContext;
Any help would be really appreciated!
The problem randomly occurs...
Crashing Location (which is a method in NSOperationQueue)
[self.requestOperationQueue addOperationWithBlock: ^{
NSArray *titleList = [[NSMutableArray alloc] init];
NSArray *allBooks = [[CoreDataManager sharedInstance] fetchBooks];
for (Book *book in allBooks)
[titleList addObject:book.title]; // program crashed here!! failed to fault the value of book.title
}];
I use managedObjectContentChild for NSEntityDescription.
However, executeFetchRequest by managedObjectContent, which is the parent of managedObjectContentChild.
Is that the potential problem?
I tried executeFetchRequest by managedObjectContentChild, however, it leads many more different issues.
However, I am binded to use managedObjectContentChild since program is running in multiple threads by create new CoreDataManager instance for individual thread. Program will run into deadlock without using children MOC.
Thanks in advance!
CoreDataManager.m
- (id)init
{
if ((self = [super init]) != nil)
{
delegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
// Writer (write data to Persistent Store Coordinator)
writerManagedObjectContext = [delegate writerManagedObjectContext];
// Parent (Fetched Result Controller)
managedObjectContext = [delegate managedObjectContext];
// Child (handling Object Context Saving for individual threads)
managedObjectContextChild = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
managedObjectContextChild.parentContext = managedObjectContext;
bookEntity = [NSEntityDescription entityForName:[Book description]
inManagedObjectContext:managedObjectContextChild];
friendEntity = [NSEntityDescription entityForName:[Friend description]
inManagedObjectContext:managedObjectContextChild];
}
return self;
}
- (NSArray *)fetchBooks
{
// Todo: fix the problem of "CoreData: error: NULL _cd_rawData but the object is not being turned into a fault"
NSArray *results = nil;
if (key == nil)
return results;
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:key ascending:ascending];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entity];
[request setPredicate:predicate];
[request setSortDescriptors:sortDescriptors];
NSError *error = NULL;
// results = [managedObjectContextChild executeFetchRequest:request error:&error];
results = [managedObjectContext executeFetchRequest:request error:&error];
if (error != NULL)
NSLog(#"Error fetching - %#", error);
return results;
}
I tried to figure out the following discussion, but still have no clue how to do it...
CoreData: error: NULL _cd_rawData but the object is not being turned into a fault
http://www.cocoabuilder.com/archive/cocoa/311615-weird-core-data-crash.html
Here is the problem,
// use this one
results = [managedObjectContextChild executeFetchRequest:request error:&error];
// not this
// results = [managedObjectContext executeFetchRequest:request error:&error];
so I use managedObjectContextChild (child MOC) instead of managedObjectContext (parent MOC) in order to create distinct MOC for each individual threads. As a rules of concurrency of CoreData.
using managedObjectContext (parent MOC) will not cause the error of object not turning to fault and crashes the app every single time, but it's serious issue if the app happened to be using the same MOC (well, there is only one managedObjectContext in this case) at the exactly the same moment even from different threads.
I have a controller that is the root of a workflow. If there is no data object for the workflow, then I create a new one, and if there is, I use the existing one. I have a property for the model object (an NSManagedObject)
#property (nonatomic, retain) Round *currentRound;
and I call the following whenever the corresponding view is shown
self.currentRound = [self.service findActiveRound];
if (!self.currentRound) {
NSLog((#"configure for new round"));
self.currentRound = [self.service createNewRound];
...
} else {
NSLog(#"configure for continue");
// bad data here
}
The problem is at the place marked in the above, sometimes the data object is corrupted. In the parts I didn't show I set the values on some text fields to represent the values in the model. Sometimes its ok, but eventually the properties on the model object are empty and things break
In the debugger, the reference to the round doesn't appear to change, but NSLogging the relevant properties shows them nullified. debugging seems to delay the onset of the corruption.
I am aware I am not saving the context...should that matter? And if so, how come it doesn't always fail the first time I come back to this controller?
My findActiveRound message is nothing special, but in case it matters
-(Round *) findActiveRound
{
NSLog(#"Create Active Round");
NSFetchRequest *request = [[NSFetchRequest alloc]init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Round" inManagedObjectContext:context];
[request setEntity:entity];
NSPredicate *pred = [NSPredicate predicateWithFormat:#"isComplete == %#", [NSNumber numberWithBool:NO]];
[request setPredicate:pred];
NSError *error = nil;
NSArray *results = [context executeFetchRequest:request error:&error];
if ([results count] == 0) {
return nil;
} else {
return [results objectAtIndex:0];
}
}
Many thanx.
EDIT FOR RESPONSE
By corrupted I mean when I try to get some simple string properties off the model object, I get nil values. So In the code above (where I think I have a round) I do stuff like
self.roundName.text = self.currentRound.venue;
self.teeSelection.text = self.currentRound.tees;
and don't see the data I entered. Since it only fails sometimes, but always fails eventually, I will see the data I entered for a while before its gone.
I'm pretty sure the context is the same. My service is a singleton and created like so
#implementation HscService
+(id) getInstance
{
static HscService *singleton = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
singleton = [[self alloc] init];
});
return singleton;
}
-(id) init
{
if (self = [super init]) {
model = [NSManagedObjectModel mergedModelFromBundles:nil];
NSPersistentStoreCoordinator *psc = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];
NSString *path = [self itemArchivePath];
NSURL *storeUrl = [NSURL fileURLWithPath:path];
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES],
NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES],
NSInferMappingModelAutomaticallyOption, nil];
NSError *error = nil;
if (![psc addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeUrl options:options error:&error]) {
[NSException raise:#"Open failed" format: #"Reason: %#", [error localizedDescription]];
}
context = [[NSManagedObjectContext alloc] init];
[context setPersistentStoreCoordinator:psc];
[context setUndoManager:nil];
}
return self;
}
-(NSString *) itemArchivePath
{
NSArray *docDirectories = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *dir = [docDirectories objectAtIndex:0];
return [dir stringByAppendingPathComponent:#"hsc1.data"];
}
and in every controller I get the singleton to perform operations. I plan on defining a delegate around round operations, and implementing it in my AppDelegate, so I'm only getting the service once in the app, but don't think that should matter for now....
Are you sure the data is actually corrupted? Managed object contexts are highly efficient, and it's normal to fault in the debugger. From the docs:
"Faulting is a mechanism Core Data employs to reduce your
application’s memory usage..."
See http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/CoreData/Articles/cdFaultingUniquing.html
If the data is actually missing and cannot be accessed by other methods, make sure you're using the same Managed Object Context to access the data. If the data has not been committed to the data store, it will not "sync" between MOCs.
I am trying to get the values I have saved in one of my core data objects, however I keep getting this error
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '+entityForName: nil is not a legal NSManagedObjectContext parameter searching for entity name 'Man''
The resulted error is occurring from the code I have written that tries to access the core data object then logg its content, as show shown below.
if ([manufDBVNumber isEqualToNumber:currentDBVersion]) {
// READ FROM CORE DATA
// WRITE TO CORE DATA
NSManagedObjectContext *context = [self managedObjectContext];
NSLog(#"%#", context);
// Test listing all FailedBankInfos from the store
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Man" inManagedObjectContext:self.managedObjectContext]; // This is where the error is happening.
[fetchRequest setEntity:entity];
NSError *error;
NSMutableArray *manufacturerDictionaryArray = [[NSMutableArray alloc] init];
NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
for (Manuf *manuf in fetchedObjects) {
NSLog(#"MANA: %#", man.mana);
NSLog(#"SURGE: %#", man.surge);
NSLog(#"HEALTH: %#", manuf.health);
etc//....
This is how I have synthesized the managedObjectContext and fetchedResultsController
// Core-Data variables used for fetching and managing the object
#synthesize fetchedResultsController = __fetchedResultsController;
#synthesize managedObjectContext = __managedObjectContext;
Any help would be greatly appreciated.
You do this:
NSManagedObjectContext *context = [self managedObjectContext];
But then you access this:
self.managedObjectContext
Assuming your NSLog(#"%#", context); shows a valid NSManagedObjectContext, then I would do this:
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Man" inManagedObjectContext:context];
Or, change your original declaration to:
self.managedObjectContext = [self managedObjectContext];
In your viewController.m implementation file, right under this bit of code:
- (void)viewDidLoad
{
add this bit of code:
id delegate = [[UIApplication sharedApplication] delegate];
self.managedObjectContext = [delegate managedObjectContext];