I am trying my hand at some very basic implementation of MagicalRecord to get the hang of it and run into the following.
When I save an entry and then fetch entries of that type it will come up with the entry I just saved. However, when I save the entry, close the app, start it again, and then fetch, it comes up empty.
Code for saving:
- (void)createTestTask{
NSManagedObjectContext *localContext = [NSManagedObjectContext contextForCurrentThread];
Task *task = [Task createInContext:localContext];
task.tName = #"First Task";
task.tDescription = #"First Task created with MagicalRecord. Huzzah!";
NSError *error;
[localContext save:&error];
if (error != Nil) {
NSLog(#"%#", error.description);
}
}
Code for fetching: (all I want to know here if anything is actually saved)
- (void) fetchTasks{
NSArray *tasks = [Task findAll];
NSLog(#"Found %d tasks", [tasks count]);
}
I am sure I am missing something here, but not anything I can seem to find on stackoverflow or in the Tutorials I looked at.
Any help is welcome.
I have to ask the obvious "Is it plugged in" question: Did you initialize the Core Data Stack with one of the +[MagicalRecord setupCoreDataStack] methods?
Did your stack initialize properly? That is, is your store and model compatible? When they aren't, MagicalRecord (more appropriately, Core Data) will set up the whole stack without the Persistent Store. This is annoying because it looks like everything is fine until it cannot save to the store...because there is no store. MagicalRecord has a +[MagicalRecord currentStack] method that you can use to examine the current state of the stack. Try that in the debugger after you've set up your stack.
Assuming you did that, the other thing to check is the error log. If you use
[localContext MR_saveToPersistentStoreAndWait];
Any errors should be logged to the console. Generally when you don't see data on a subsequent run of your app, it's because data was not saved when you thought you called save. And the save, in turn, does not happen because your data did not validate correctly. A common example is if you have a required property, and it's still nil at the time you call save. "Normal" core data does not log these problems at all, so you might think it worked, when, in fact, the save operation failed. MagicalRecord, on the other hand, will capture all those errors and log them to the console at least telling you what's going on with your data.
When i have started with magical record I was also facing this problem, problem is context which you are using to save data. here is my code which might help you
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
NSArray *userInfoArray = [UserBasicInfo findByAttribute:#"userId" withValue:[NSNumber numberWithInt:loggedInUserId] inContext:localContext];
UserBasicInfo* userInfo;
if ([userInfoArray count]) {
userInfo = [userInfoArray objectAtIndex:0];
} else {
userInfo = [UserBasicInfo createInContext:localContext];
}
userInfo.activeUser = [NSNumber numberWithBool:YES];
userInfo.firstName = self.graphUser[#"first_name"];
userInfo.lastName = self.graphUser[#"last_name"];
userInfo.userId = #([jsonObject[#"UserId"] intValue]);
userInfo.networkUserId = #([jsonObject[#"NetworkUserId"] longLongValue]);
userInfo.userPoint = #([jsonObject[#"PointsEarned"] floatValue]);
userInfo.imageUrl = jsonObject[#"Picturelist"][0][#"PictureUrL"];
userInfo.imageUrlArray = [NSKeyedArchiver archivedDataWithRootObject:jsonObject[#"Picturelist"]];
} completion:^(BOOL success, NSError *error) {
}];
Use this when your done
[[NSManagedObjectContext MR_defaultContext]saveToPersistentStoreAndWait];
Related
I have an entity (TestEntity) which contains a "Transformable" attribute which holds an object (MyObjectClass). On initial load, the transformable saves correctly; initialised as below:
TestEntity *test = (TestEntity *)[NSEntityDescription insertNewObjectForEntityForName:ENTITY[<Int>] inManagedObjectContext:temporaryContext];
test.transformableAttr = [[MyObjectClass alloc] initWithObject:obj];
However, when I fetch an object (I fetch as dictionary with NSDictionaryResultType) and update its "Transformable" attribute,
MyObjectClass *my_obj = ....
dict[#"transformableAttr"] = my_obj
it saves successfully but when I fetch it again I get nil for the "Transformable" attribute.
Now this only happens with "NSBatchUpdateRequest" because when I save using the MOC
TestEntity *test = ....
test.transformableAttr = updated_object
it saves successfully and I can access the updated attribute when fetched again.
Can anyone please explain? Does it mean that NSBatchUpdateRequest does not Transformable?
My NSBatchUpdateRequest code:
[context performBlock:^{
NSError *requestError = nil;
NSBatchUpdateRequest *batchRequest = [NSBatchUpdateRequest batchUpdateRequestWithEntityName:entity];
batchRequest.resultType = NSUpdatedObjectIDsResultType;
batchRequest.propertiesToUpdate = properties;
NSBatchUpdateResult *result = nil;
SET_IF_NOT_NIL(batchRequest.predicate, predicate)
#try {
result = (NSBatchUpdateResult *)[context executeRequest:batchRequest error:&requestError];
if (requestError != nil){
// #throw
}
if ([result.result respondsToSelector:#selector(count)]){
__block NSInteger counter = [result.result count];
if (counter > 0){
[managedObjectContext performBlock:^{
for(NSManagedObjectID *objectID in result.result){
NSError *faultError = nil;
NSManagedObject *object = [managedObjectContext existingObjectWithID:objectID error:&faultError];
if (object && faultError == nil) {
[managedObjectContext refreshObject:object mergeChanges:YES];
}
counter--;
if (counter <= 0) {
// complete
}
else{
// Wait.
}
}
}];
}
else{
// No Changes
}
}
else {
// No Changes
}
}
#catch (NSException *exception) {
#throw;
}
}];
The documentation doesn't seem to call out this particular scenario, but I'm not surprised that it doesn't work. An NSBatchUpdateRequest is described as [emphasis mine]:
A request to Core Data to do a batch update of data in a persistent store without loading any data into memory.
Transformables work by converting to/from Data in memory. If your class conforms to NSCoding, the coding/decoding happens in memory, because SQLite doesn't know about NSCoding or your classes.
Your original assignment works because Core Data converts the value of transformableAttr to Data in memory and then saves the bytes of the Data to the persistent store. In the batch update, the objects aren't loaded into memory, so the transformation can't run, so the update doesn't work as you'd expect.
It's disappointing that Core Data doesn't make this clearer. Look in the Xcode console to see if it warns you about this. If it doesn't, please file a bug with Apple, because though I don't expect this to work, it's also not good for it to fail silently.
If you want to use batch updates, you'll have to convert your value in code before running the update. I'm not 100% certain how this will work but if your value conforms to NSCoding you'll start with
let transformedData: Data = NSKeyedArchiver.archivedData(withRootObject:transformableAttr)
What you do then is where I'm not sure. You might be able to use transformedData as the new value. Or you might have to access its bytes and use them somehow-- maybe using withUnsafeBytes(_:). You'll probably run into trouble because transformableAttr is not a Data, so it may get messy. It seems that batch updates aren't designed to work well with transformables.
I'll try to keep this brief but basically, I have an app that, in a certain mode, can near-continuously log location and other data, and snap photos (using AVFoundation) and store it all in Core Data. I discovered, as suspected, that all of this would need to be threaded...otherwise the UI gets extremely sluggish.
I have never attempted to combine Core Data with concurrency before so I read up on it as best I could. I feel like I understand what I'm supposed to do, but for someone reason it's not right. I crash with this error: "Illegal attempt to establish relationship "managedDataPoint" between objects in different contexts. I know what this means, but I thought what I have below would avoid this (I'm following what I've read)...since I get an Object ID reference from the main context, and use that to grab a new reference to the object and pass it to the "temp" context...but that isn't working as Core Data still claims I'm attempting to create a relationship across contexts (where?). Appreciate any help. Thank you!
-(void)snapPhotoForPoint:(ManagedDataPoint*)point
{
if (!_imageCapturer)
{
_imageCapturer = [[ImageCapturer alloc] init];
}
if (!_tempContext) {
_tempContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
_tempContext.parentContext = self.managedObjectContext;
}
__block NSManagedObjectID* pointID = [point objectID];
[_tempContext performBlock:^{
NSError *error = nil;
Photo *newPhoto = [NSEntityDescription insertNewObjectForEntityForName:#"Photo" inManagedObjectContext:_tempContext];
UIImage *image = [_imageCapturer takePhoto];
newPhoto.photoData = UIImageJPEGRepresentation(image, 0.5);
ManagedDataPoint *tempPoint = (ManagedDataPoint*)[self.managedObjectContext objectWithID:pointID];
newPhoto.managedDataPoint = tempPoint; // *** This is where I crash
if (![_tempContext save:&error]) { // I never get here.
DLog(#"*** ERROR saving temp context: %#", error.localizedDescription);
}
}];
}
Shouldn't
ManagedDataPoint *tempPoint = (ManagedDataPoint*)[self.managedObjectContext objectWithID:pointID];
not be
ManagedDataPoint *tempPoint = (ManagedDataPoint*)[_tempContext objectWithID:pointID];
Otherwise you are working with different contexts! Also you should check if objectID is a temporary ID and acquire a "final" one in case of.
I have a tableview in my app that contains a NSFetchedResultsController to load in some CoreData objects.
As the table builds in cellForRowAtIndexPath:, for each cell I must do a fetch to get some other info from another object.
The table is filled with UserTasks, and I must get some info from a UserSite (UserTask contains a siteID attribute)
I am getting the UserSite info in a background thread, and using a temporary context. It works fine, but it still wants to lag the UI a bit when scrolling.
Site *site = [_scannedSites objectForKey:task.siteID];
if(!site)
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
AppDelegate *ad = [AppDelegate sharedAppDelegate];
NSManagedObjectContext *temporaryContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSConfinementConcurrencyType];
temporaryContext.persistentStoreCoordinator = ad.persistentStoreCoordinator;
Site *site2 = [task getSiteWithContext:temporaryContext];
if(site2)
{
[ad.managedObjectContext performBlock:^{
Site *mainContextObject = (Site *)[ad.managedObjectContext objectWithID:site2.objectID];
[_scannedSites mainContextObject forKey:task.siteID];
}];
dispatch_async(dispatch_get_main_queue(), ^{
Site *newSite = [_scannedSites objectForKey:task.siteID];
cell.lblCustName.text = newSite.siteName;
cell.lblAddr.text = [NSString stringWithFormat:#"%# %#, %#", newSite.siteAddressLine1, newSite.siteCity, newSite.siteState];
cell.lblPhone.text = [self formatPhoneNum:newSite.phone];
});
}
else
{
dispatch_async(dispatch_get_main_queue(), ^{
cell.lblCustName.text = #"";
cell.lblAddr.text = #"";
cell.lblPhone.text = #"";
});
}
});
}
else
{
cell.lblCustName.text = site.siteName;
cell.lblAddr.text = [NSString stringWithFormat:#"%# %#, %#", site.siteAddressLine1, site.siteCity, site.siteState];
cell.lblPhone.text = [self formatPhoneNum:site.phone];
}
As you can see, if you dont already have the UserSite info for a task in _scannedSites, a background thread gets kicked off which gets the UserSite for that task, stores it, and then on the main thread fills in the details.
Like I said there is a pretty annoying lag when scrolling... which I hoped to avoid by doing the work in the background.
Am I going about this the wrong way?
Thanks, any advice is appreciated.
EDIT
I created a relationship in CoreData and I am now using that in cellForRowAtIndexPath. If it does not exist yet, I create it. This is working much better.
Site *site = task.site;
if(!site)
{
AppDelegate *ad = [AppDelegate sharedAppDelegate];
NSManagedObjectContext *temporaryContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
temporaryContext.persistentStoreCoordinator = ad.persistentStoreCoordinator;
[temporaryContext performBlock:^{
Site *tempContextSite = [task getSiteWithContext:temporaryContext];
[ad.managedObjectContext performBlock:^{
Site *mainManagedObject = (Site *)[ad.managedObjectContext objectWithID:tempContextSite.objectID];
task.site = mainManagedObject;
NSError *error;
if (![temporaryContext save:&error])
{
}
[ad.managedObjectContext performBlock:^{
NSError *e = nil;
if (![ad.managedObjectContext save:&e])
{
}
dispatch_async(dispatch_get_main_queue(), ^{
cell.lblCustName.text = mainManagedObject.siteName;
cell.lblAddr.text = [NSString stringWithFormat:#"%# %#, %#", mainManagedObject.siteAddressLine1, mainManagedObject.siteCity, mainManagedObject.siteState];
cell.lblPhone.text = [self formatPhoneNum:mainManagedObject.phone];
});
}];
}];
}];
}
else
{
cell.lblCustName.text = site.siteName;
cell.lblAddr.text = [NSString stringWithFormat:#"%# %#, %#", site.siteAddressLine1, site.siteCity, site.siteState];
cell.lblPhone.text = [self formatPhoneNum:site.phone];
}
If UserTask relates to UserSite, the usual Core Data approach would be to create a relationship between the two and then use that relationship at run time. So, UserTask would have a property named site, and you'd just ask a specific instance for the value of that property. An ID attribute might still exist but would only be used when syncing with some external data store (like a server API).
Storing IDs and looking up objects like this is a fundamentally awkward approach that's pretty much designed to do a lot of unnecessary work at run time. It avoids all of the conveniences that Core Data tries to provide, doing things the hard way instead. Doing this work while the table is scrolling is also about the worst possible time, because it's when a performance issue will be most noticeable.
If you must do it this way for some reason, you could optimize things by looking up all of the UserSite instances in advance instead of while the table is scrolling. If you know all of the UserTask instances, go get all the sites in one call when the view loads.
It is a bad idea to send of asynchronous tasks in cellForRowAtIndexPath:. If the user scrolls there are going to be a whole bunch of threads created which are maybe not even necessary.
It would be much better to have a background process that fetches the information you want and then notifies the UI to update itself if needed. This is pretty standard stuff, you will find many examples for solid implementations easily.
I have an app that works fine on the simulator 6.1, works fine on the iPhone5 and iPad3 on iOS6.1 but when run on iPhone4S it crashes in this method with Exc Bad Access:
-(void)parsePlistIntoCD{
self.managedObjectContext = [[SDCoreDataController sharedInstance] backgroundManagedObjectContext];
// 3: Now put the plistDictionary into CD...create get ManagedObjectContext
NSManagedObjectContext *context = self.managedObjectContext;
NSError *error;
//Create Request & set Entity for request
NSFetchRequest *holidayRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *topicEntityDescription = [NSEntityDescription entityForName:#"Holiday" inManagedObjectContext:context];
[holidayRequest setEntity:topicEntityDescription];
//Create new NSManagedObject
//Holiday *holidayObjectToSeed = nil;
Holiday *newHoliday = nil;
//Execute fetch just to make sure?
NSArray *holidayFetchedArray = [context executeFetchRequest:holidayRequest error:&error];
**if (error) NSLog(#"Error encountered in executing topic fetch request: %#", error); // if I comment this line out it reaches as far as the next bold line**
// No holidays in database so we proceed to populate the database
if ([holidayFetchedArray count] == 0) {
//Get path to plist file
NSString *holidaysPath = [[NSBundle mainBundle] pathForResource:#"PreloadedFarsiman" ofType:#"plist"];
//Put data into an array (with dictionaries in it)
NSArray *holidayDataArray = [[NSArray alloc] initWithContentsOfFile:holidaysPath];
**NSLog(#"holidayDataArray is %#", holidayDataArray);**
//Get number of items in that array
int numberOfTopics = [holidayDataArray count];
//Loop thru array items...
for (int i = 0; i<numberOfTopics; i++) {
//get each dict at each node
NSDictionary *holidayDataDictionary = [holidayDataArray objectAtIndex:i];
//Insert new object
newHoliday = [NSEntityDescription insertNewObjectForEntityForName:#"Holiday" inManagedObjectContext:context];
//Parse all keys in each dict object
[newHoliday setValuesForKeysWithDictionary:holidayDataDictionary];
//Save and or log error
[context save:&error];
if (error) NSLog(#"Error encountered in saving topic entity, %d, %#, Hint: check that the structure of the pList matches Core Data: %#",i, newHoliday, error);
};
}
//set bool that specifies the coredata has been populated from plist already
NSString *bundleVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:(NSString *)kCFBundleVersionKey];
NSString *appFirstStartOfVersionKey = [NSString stringWithFormat:#"first_start_%#", bundleVersion];
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
[prefs setObject:#(YES) forKey:appFirstStartOfVersionKey];
[prefs synchronize];
}
Why only on 4S? It gives no console log and the last know method traversed in this one above. Here is a pic:
And as mentioned above, if I uncomment that NSLog line it reaches as far as logging the array, as shown in the pic. If I leave it in it stops at that line.
NSArray *holidayFetchedArray = [context executeFetchRequest:holidayRequest error:&error];
if (error)
NSLog(#"Error encountered in executing topic fetch request: %#", error);
No no no no no. I know this is a difficult pattern, but please let's try to do it right. Not if (error). error could be anything (esp. under non-ARC). The test is if (!holidayFetchedArray).
For all of these methods that return a value and also take an NSError** by indirection, you test the result to see if it is nil. If it is, then there was an error because returning nil is the sign that there was an error. Then and only then you may touch the error meaningfully.
The docs are always quite clear about this, though it is true that a fog can come over one's eyes at the critical instance, so I've added some comments in italic brackets to call out the key points:
request
A fetch request that specifies the search criteria for the
fetch.
error
If there is a problem executing the fetch, upon return
contains an instance of NSError that describes the problem.
[And if there is no problem executing the fetch, contains garbage so don't touch it!]
Return Value
An array of objects that meet the criteria specified by request
fetched from the receiver and from the persistent stores associated
with the receiver’s persistent store coordinator. If an error occurs,
returns nil. [And that, forsooth, is the sign that an error did occur.] If no objects match the criteria specified by request,
returns an empty array.
This might be the cause of your trouble or it might not, but you must fix it now. I want you to go through all your code looking for NSError* variables declared for use in this pattern and fix all of them! Thank you. Here endeth the lesson.
This is a stab in the dark b/c you haven't identified the line of code where the crash occurs. I recommended you set a breakpoint and then step through until you hit the crash. If what I say below doesn't resolve the issue then edit your post and add more info about where the crash occurs and we'll go from there.
Here's something to check. The line:
[newHoliday setValuesForKeysWithDictionary:holidayDataDictionary]
may be crashing if any of the keys in holidayDataDictionary aren't valid for an instance of the Holiday class.
With regard to your question about why it only crashes on your iPhone 4S there's not enough data to explain that yet. Depending on how you've been doing testing and core data migration (if at all) you may have a model inconsistency on that device, though I don't see anything in the screen shot you posted pointing me in that direction. I would try uninstalling/reinstalling your app and see if the crash is still iPhone 4S only.
What kind of errors can -[NSManagedObjectContext executeFetchRequest:error:] and -[NSFetchedResultsController performFetch:] return to the user and how should they be handled? I cannot find anything in the documentation about the possible errors for these methods. Also none of the error codes defined in CoreData/CoreDataErrors.h seem to apply to fetching.
Right now my error handling for Core Data fetches is just a NSAssert like this:
NSError *fetchError = nil;
NSArray *fetchedResults = [context executeFetchRequest: request error: &fetchError];
NSAssert( fetchedResults, #"Error fetching: %#", fetchError );
While testing I never had this assertion fail, but that doesn’t mean that this cannot fail. What are the best practices to handle those errors gracefully?
When executing a fetch request, I populate the expected array with an empty array instead of leaving it nil.
NSError *error;
NSArray *array = [context executeFetchRequest:request error:&error];
if (array == nil)
{
NSLog(#"Error retrieving array of values %#", error);
array = [NSArray array];
}
If you ever want to test your error handling, this answer details how to implement an NSPeristentStore that will give you an error every time.
Core Data Unit Testing - Unsure how to trigger error case in executeFetchRequest:error:
You already found CoreDataErrors.h, also see the Core Data Constants Reference
Possible errors that could occur would be SQLite query errors for example. I vaguely remember seeing something like this, when I used an operation in a predicate that was translated into something not supported by the SQLite version backing core-data.
If caught during development you use the NSError purely to debug. If this happens at run time in an already released application an option would be to fail gracefully and if possible be ask the user to specify a different search format.
First thing is to capture the error, but it entirely depends on the context of your code as to when you want to handle it gracefully or when you want to assert and stop anything else happening if things are going wrong.
Also remember that fetchedResults can return not nil, with no results (count == 0), which is not an error but you obviously might want to code against that.
NSError *fetchError = nil;
NSArray *fetchedResults = [context executeFetchRequest: request error: &fetchError];
if (fetchError) {
NSLog(#"Error with fetch: %#",error);
// Assert or do whatever is required ..
}
// Continue as normal ..
I always pass these errors up to the NSResponder chain, thus:
NSManagedDocument _document;
NSManagedObjectContext _moc;
NSError *error = nil;
NSArray *result = [_managedObjectContext executeFetchRequest:fr error:&error];
if (fetchedResults == nil && error) {
[_document presentError:error];
// or, if this isn't a document-based app, you can do
// [NSApp presentError:error];
// or, if this method is in an IBAction you can just do
// [sender presentError:error];
// and it'll just do the right thing
}
The default implementation in NSManagedDocument does an okay job of presenting these errors, except for situations when you are saving a doc and get multiple validation errors, in which case you need to write something special.
When in doubt, present NSError as soon as possible, and if you find yourself hard-coding a lot of retval checking for NSError, you problem might be more in what you're sending the error-returning function in the first place.
NSErrors, in general, are for users to resolve; NSExceptions are what the framework uses to let developers know what they need to deal with.