I am trying to understand iOS Core data transient properties and am having trouble understanding some behavior.
Setup
I have two contexts a Main and a Private context. I call them mainContext and threadedContext .
The threaded context is the parent context and the main context is the child context. (I did it this way because my threaded context alters the model far more frequently than my main thread and UI do.
I have transient properties who's value I need to pass through contexts.
I find that sometimes I loose the value and sometimes I don't depending on how I run things.
Sample
This code has been simplified to show the problem. I have a Person object. The Person object has a transient entity called "other" of which you will see I assign an Other object to it that has a couple simple properties, nothing more.
- (void)case1
{
NSManagedObjectContext *mainThreadContext = [AppDelegate appDelegate].mainThreadContext;
NSManagedObjectContext *threadedContext = [AppDelegate appDelegate].threadedContext;
__block NSManagedObjectID *objectID = nil;
[mainThreadContext performBlockAndWait:^{
//create
Person *aPerson = [self createAPersonOnContext:mainThreadContext];
//setup
Other *other = [[Other alloc] init];
aPerson.other = other;
aPerson.other.favoriteColor = #"Blue";
aPerson.other.city = #"Provo";
//save
NSError *error = nil;
[mainThreadContext save:&error];
objectID = aPerson.objectID;
NSLog(#"%#",aPerson);
}];
}
When I retrieve the Object like this the person.other property is still set (note that I am saving AFTER I retrieve the object:
[threadedContext performBlockAndWait:^{
Person *aPerson = [self getPersonOnContext:threadedContext withID:objectID];
NSError *threadedError = nil;
[threadedContext save:&threadedError];
NSLog(#"threaded %#", aPerson);
}];
When I retrieve the Object like this the person.other is no longer set (note that I am saving BEFORE I retrieve the object)
[threadedContext performBlockAndWait:^{
NSError *threadedError = nil;
[threadedContext save:&threadedError];
Person *aPerson = [self getPersonOnContext:threadedContext withID:objectID];
NSLog(#"threaded %#", aPerson);
}];
I've tried different things including refreshObject:mergChanges:
I've tried to watch when objects fault but that didn't appear to be helpful.
Are transient values stored in a given context (assuming I have saved, or maybe not given the issue I am seeing) even if no model object is currently instantiated?
For those who feel they need more...
The method getPersonOnContext:WithID looks like this:
- (Person *)getPersonOnContext:(NSManagedObjectContext *)context withID:(NSManagedObjectID *)ID
{
__block Person *person = nil;
[context performBlockAndWait:^{
person = (Person *)[context objectWithID:ID];
}];
return person;
}
The createAPersonOnContext: looks like this:
- (Person *)createAPersonOnContext:(NSManagedObjectContext *)context
{
__block Person *person = nil;
[context performBlockAndWait:^{
person = (Person *)[NSEntityDescription insertNewObjectForEntityForName:#"Person"
inManagedObjectContext:context];
person.firstName = #"matt";
person.lastName = #"ZZZ";
}];
return person;
}
I just wanted to hide this code to help bring attention to the problem it self.
If you want to experiment with this I have it on github: https://github.com/mcmurrym/CoreDataBehaviors
Update:
It appears that when I save before using the ID to retrieve the object in the threaded context that it is faulting the Person object which destroys the transient values. If I retrieve the object in the threaded context before saving, the transient value is preserved because the object is not faulted.
maxpower,
Transients are quite simple. They are properties that are always non-existent in the backing store. Hence, the fact that you ever see them is because you are using a child MOC and have externally assigned those values. To ensure that a transient is always valid, you need to consider implementing the -awakeFromInsert, -awakeFromFetch, -prepareForDeletion, -didTurnIntoFault and -willTurnIntoFault methods.
Andrew
Related
Hi I'm working with Core Data IOS using magical record library for objective C. The library have many NSManageObjectContext initiation. What should we use in order to keep app perfomance and good user experience?
there are many
+ [NSManagedObjectContext MR_newContext]: Sets the default context as it's parent context. Has a concurrency type of NSPrivateQueueConcurrencyType.
+ [NSManagedObjectContext MR_newMainQueueContext]: Has a concurrency type of NSMainQueueConcurrencyType.
+ [NSManagedObjectContext MR_newPrivateQueueContext]: Has a concurrency type of NSPrivateQueueConcurrencyType.
+ [NSManagedObjectContext MR_newContextWithParent:…]: Allows you to specify the parent context that will be set. Has a concurrency type of NSPrivateQueueConcurrencyType.
+ [NSManagedObjectContext MR_newContextWithStoreCoordinator:…]: Allows you to specify the persistent store coordinator for the new context. Has a concurrency type of NSPrivateQueueConcurrencyType.
What context initiation is good one?
For example this function deal with JSON response and save record to database whenever successfully receive the resonse
NSManagedObjectContext *localContext = [NSManagedObjectContext MR_context];
[Stamp MR_truncateAllInContext:localContext];
[responseJSON[#"root"] enumerateObjectsUsingBlock:^(id attributes, NSUInteger idx, BOOL *stop) {
Stamp *stamp = [Stamp MR_createEntityInContext:localContext];
[stamp setOrderingValue:idx];
[stamp updateWithApiRepresentation:attributes];
}];
[localContext MR_saveToPersistentStoreWithCompletion:^(BOOL success, NSError *error) {
if (completionBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
completionBlock(!error, error);
});
}
}];
And this function perform fetch request
+ (NSArray *)yearsDropDownValues
{
NSManagedObjectContext *moc = [NSManagedObjectContext MR_rootSavingContext];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [Stamp entityInManagedObjectContext:moc];
request.entity = entity;
request.propertiesToFetch = #[StampAttributes.year];
request.returnsDistinctResults = YES;
request.resultType = NSDictionaryResultType;
request.sortDescriptors = #[[[NSSortDescriptor alloc] initWithKey:StampAttributes.year ascending:NO]];
NSArray *years = [moc executeFetchRequest:request error:nil];
NSMutableArray *res = [NSMutableArray array];
for (NSDictionary *year in years) {
[res addObject:#{#"code": [NSString stringWithFormat:#"%# Collections", year[#"year"]], #"value": year[#"year"] }];
}
return res;
}
Any help is much appreciate. Thanks
Before I start, I think there are two more contexts you should know and understand, they are created automatically when you use MagicalRecord's default way to setup your CoreData stack:
MR_rootSavingContext, it is a private queue context directly connect to the coordinator, usually it will be your root context.
MR_defaultContext, it is a main queue context, which has MR_rootSavingContext as its parent, usually it will be your UI context, use it to fetch and show your data on the screen.
Now I will explain those five context one by one:
MR_newContext, a new private queue context which has MR_defaultContext as its parent context. It is equivalent of calling [NSManagedObjectContext MR_newContextWithParent:[NSManagedObjectContext ME_defaultContext]]. This type of context is good for heavy batch operations, like inserting, updating, deleting 100s of objects. Since all the operations are running on the background thread, the UI will not be blocked. However the drawbacks are, it brings extra complexity, especially when you have more than one of such context, conflicts will probably occur when save these contexts.
MR_newMainQueueContext, a new main queue context which does not have parent context. It is the sibling of MR_rootSavingContext, as they are connecting to the same NSPersistentStoreCoordinator. All the operations you do on this type of context will block the UI, so do not do any heavy work on this context.
MR_newPrivateQueueContext, similar to MR_newContext, but it does not have parent context. It is the sibling of MR_rootSavingContext.
MR_newContextWithParent, a convenient way to create a private queue context as well as specify the parent context.
MR_newContextWithStoreCoordinator, a new private queue context which is using a specified NSPersistentStoreCoordinator. From my point of view, only if you know how to use contexts with different coordinator properly, do not use it.
So in a word, there is no good or bad among these contexts, you need to choose the right one based on your requirement.
I'm trying to best format my project's use of RestKit and Core Data. There are a couple of things that I've got working, but I have a feeling they are implemented poorly and potentially thread unsafe... I have an object that manages all of my data transfer, storage, etc., which has a function that sets up restkit. I have an instance variable that I use for RKObjectManager, and in this setup function I create the objectStore, setup all the attribute mappings, create the persistent store, etc., - all of the normal restkit setup code. Outside of this function, the only thing available to this object is the _objectManager instance variable, which I've been using for NSFetchRequests and such.
There are two things I want to make sure I'm implementing properly, fetching managed objects, and savings changes to managed objects.
If I want to update a property on an object, I've been doing this:
object.whatever = #"something here";
NSError *error;
if (![object.managedObjectContext save:&error]) {
// log the error here
}
Is this the proper way to update/save a property on an object? Is accessing the object's managed object context directly save to do at any point in the code, or is this something that should be done only in the background/foreground? My current implementation could potentially have this called in both the background and foreground and I just want to make sure this is acceptable.
When I want to fetch an object, I wrote a function that takes an entity name, an array of predicates, and a sort descriptor as parameters so it can be reused:
NSManagedObjectContext *managedObjectContext = // I DONT KNOW WHAT TO PUT HERE! //
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:entityName inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
NSPredicate *compoundPredicate = [NSCompoundPredicate andPredicateWithSubpredicates:predicates];
[fetchRequest setPredicate:compoundPredicate];
NSError *error;
NSArray *fetchedRecords = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
if (error) {
// log error
}
// if we were given a sort descriptor, sort the array appropriately
if (sortDescriptor) {
fetchedRecords = [fetchedRecords sortedArrayUsingDescriptors:#[sortDescriptor]];
}
return fetchedRecords;
My problem here is creating/accessing the correct managed object context. How should I do this? Do I access some property on the RKObjectManager I created before such as:
_objectManager.managedObjectStore.mainQueueManagedObjectContext
or is that not thread safe because its for the main thread? What can I do to make sure I'm using the right managed object context, and that it is thread safe? I was using:
_objectManager.managedObjectStore.persistentStoreManagedObjectContext
but I was told this was definitely not best practice and was not thread safe, so I'm trying to determine the best solution.
EDIT - perhaps I can call this function to get the context whenever I want to fetch objects?
- (NSManagedObjectContext *)getManagedObjectContext {
if ([NSThread isMainThread]) {
return _objectManager.managedObjectStore.mainQueueManagedObjectContext;
}
else {
return [_objectManager.managedObjectStore newChildManagedObjectContextWithConcurrencyType:NSPrivateQueueConcurrencyType tracksChanges:YES];
}
}
For saving, instead of this:
if (![object.managedObjectContext save:&error]) {
you should do:
if (![object.managedObjectContext saveToPersistentStore:&error]) {
so that the changes are persisted right up the chain to the on-disk store. You should only be doing this on the thread that created / fetched the managed object (thus the thread ownership of the MOC is maintained).
Foreground / background isn't important so much as which MOC is used by each thread. If the MOC thread ownership is respected then you should be fine.
Same applies to fetching. For UI updates, you must use the main thread and the mainQueueManagedObjectContext. You should never directly use the persistentStoreManagedObjectContext. For arbitrary background threads you should be asking the managed object store to create a new child managed object context for you and using that.
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 import the logged in user's data from server into a Core Data Entity called "User". I also keep a reference of this specific User object onto my AppDelegate (as a property) so I can access it elsewhere in my app. The problem I am facing is, when I push another view controller and try to access appdelegate.loggedInUser.id , I see that "id" is nil. Debugger shows this for the object :
$24 = 0x0b28ad30 <User: 0xb28ad30> (entity: User; id: 0xb261160 <x-coredata:///User/tC48E8991-B8A6-4E68-9112-93F9F21DB5382> ; data: <fault>)
My understanding was that the Core Data framework would fire the fault the moment I try to access one of the properties of this object. I am confused as to why me accessing the "id" property of the user is not firing a fault in this case?
EDIT:
This is how create and use the loggedInUser object :
//method to get bgContext
+(NSManagedObjectContext *)getContextOnBgWithParentSetToMainMOC
{
NSManagedObjectContext *tmpContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[tmpContext setParentContext:[Utils getAppDelegate].managedObjectContext];
return tmpContext;
}
//in App Delegate
NSManagedObjectContext *bgContext = [NSManagedObjectContext getContextOnBgWithParentSetToMainMOC];
self.loggedInUser = [User importFromObject:loggedInUserData inContext:bgContext completionBlock:^(NSManagedObjectContext *theContext, NSManagedObject *theManagedObjectWithValuesImported) {}];
//In User.m file
+ (User *)importFromObject:(NSDictionary *)dictionary inContext:(NSManagedObjectContext *)context completionBlock:(TemporaryContextImportReturnBlock)block {
if ( !context ){
context = [NSManagedObjectContext getContextOnBgWithParentSetToMainMOC];
}
NSManagedObjectContext *localContext = context;
User *newUserEntity = [NSEntityDescription insertNewObjectForEntityForName:#"User" inManagedObjectContext:localContext];
NSArray *emailsArray = [dictionary objectForKey:#"emails"];
NSString *emailsString = #"";
if ([emailsArray count] > 0){
emailsString = [emailsArray componentsJoinedByString:#","];
}
newUserEntity.emails = emailsString;
newUserEntity.id = [dictionary objectForKey:#"id"];
newUserEntity.n = [dictionary nonNullObjectForKey:#"n"];
return newUserEntity;
}
//Access in one of the view controllers
User *loggedInUser = [Utils getAppDelegate].loggedInUser;
// loggedInUser.id /*nil*/
I have the same problem. It turns out, according to this answer, which references the Apple docs, that an NSManagedObject does not hold a strong reference to its NSManagedObjectContext as you might expect. I suspect that if you inspect your object when it doesn't fire the fault properly that [myObject managedObjectContext] == nil.
I don't know what best practices are here. The obvious (but potentially difficult) solution is to find out why your MOC is being deallocated. As an alternative, although I'm unsure whether it's safe to do, you could retain the MOC from each NSManagedObject instance. (I have question about that open here.)
make sure, that you do not call
[managedObjectContext reset];
somewhere. From Apple doc:
Returns the receiver to its base state.
All the receiver's managed objects are “forgotten.” If you use this method, you should ensure that you also discard references to any managed objects fetched using the receiver, since they will be invalid afterwards.
Those "orphaned" managed object's managedObjectContext property will change to nil and they will not be able to fire faults anymore.
I am new to the IOS programming, currently learning core data, I went into running the code where i need to save only specific objects in core data. So how can i do that?
According to the scenario, i have data from server as well as local storage (core data), but when user close the app (went to background) I want to store the data in the server(if net available) if not then in the local storage (but selected only - means specific data should be stored, there are objects which came from online server which i dont want to store on local).
Please let me know any solution if possible.
Regards
Nisar Ahmed
I see two ways to achieve this:
Iterate through inserted and updated objects and revert those you do not wont to save. Inserted objects should be deleted, updated should be refreshed:
for (NSManagedObject* obj in [self.managedObjectContext insertedObjects]) {
if (/*Shouldn't be saved*/) {
[self.managedObjectContext deleteObject:obj];
}
}
for (NSManagedObject* obj in [self.managedObjectContext updatedObjects]) {
if (/*Shouldn't be saved*/) {
[self.managedObjectContext refreshObject:obj mergeChanges:NO];
}
}
Create separate managed object context. Recreate objects that you want to save in new context and then save it.
NSManagedObjectContext* newContext = [[NSManagedObjectContext alloc] init];
[newContext setPersistentStoreCoordinator:[self.managedObjectContext persistentStoreCoordinator]];
for (NSManagedObject* obj in objectsWantToSave) {
NSEntityDescription* entity = [obj entity];
NSDictionary* valuesByKeys = [obj dictionaryWithValuesForKeys:[[entity attributesByName] allKeys]];
NSManagedObject* objCopy = [NSEntityDescription insertNewObjectForEntityForName:[entity name] inManagedObjectContext:newContext];
[objCopy setValuesForKeysWithDictionary:valuesByKeys];
}
[newContext save:NULL];
The second approach is better for my opinion.
Have a look into UIManagedDocument - http://developer.apple.com/library/ios/#documentation/uikit/reference/UIManagedDocument_Class/Reference/Reference.html
It takes care of a lot of the boilerplate involved in using core data.