RestKit 2.0 Removing RKManagedObjectStore but keeping NSManagedObjectModel - cocoa

I have a project set up where all data coming from the Server is wrote to a Core Data managed store using a managed model. I have all my entities generated from the Core Data model using mogenerator. I have all RestKit mapping integrated in to my entities.
NSError *error = nil;
NSURL *modelURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:#"dataModel" ofType:#"momd"]];
// NOTE: Due to an iOS 5 bug, the managed object model returned is immutable.
NSManagedObjectModel *managedObjectModel = [[[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL] mutableCopy];
RKManagedObjectStore *managedObjectStore = [[RKManagedObjectStore alloc] initWithManagedObjectModel:managedObjectModel];
// Initialize the Core Data stack
[managedObjectStore createPersistentStoreCoordinator];
NSPersistentStore __unused *persistentStore = [managedObjectStore addInMemoryPersistentStore:&error];
NSAssert(persistentStore, #"Failed to add persistent store: %#", error);
[managedObjectStore createManagedObjectContexts];
// Set the default store shared instance
[RKManagedObjectStore setDefaultStore:managedObjectStore];
Now there has been a change of plan due to time constraints. The data should not be stored at all. The data should be read from the server and displayed directly. No saving, no persisting. So I would like to cut out the RKManagedObjectStore, keep the entities and mappings, and read the data from 'RKMappingResult *mappingResult' when a request succeeds or a RKPaginator resutl. Example that works with RKManagedObjectStore and RKPaginator:
[objectManager addResponseDescriptor:[RKResponseDescriptor responseDescriptorWithMapping:[Friend entityMapping:objectManager.managedObjectStore]
method:RKRequestMethodAny
pathPattern:nil
keyPath:#"items"
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)]];
[objectManager setPaginationMapping:[self paginationMapping]];
self.paginator = [objectManager paginatorWithPathPattern:#"data"];
self.paginator.perPage = 20;
//Set completion block for this paginator
[self.paginator setCompletionBlockWithSuccess:^(RKPaginator *paginator, NSArray *objects, NSUInteger page) {
[weakSelf.dataArray addObjectsFromArray:objects];
} failure:^(RKPaginator *paginator, NSError *error) {
}];
However, when I start to reomve the RKManagedObjectStore I start to run into problems when mapping.
'You must provide a managedObjectStore. Invoke mappingForClass:inManagedObjectStore: instead.'
Q.1 Can I use Enitiy Mapping without RKManagedObjectStore? Am I going in the right direction.
Q.2 Can I remove the store and keep the model?
Any tips, help or examples would be great before I get too involved and go in the wrong direction.
Thanks Al

You should fight against the requirement change and use Core Data as a temporary cache of information to aid with memory management (so you can scroll up and down lists without having to have everything loaded all the time). This should not take any longer to implement...
No, you can't use RKEntityMapping without an RKManagedObjectStore.
You could keep the model but you wouldn't be able to use it (managed objects need to be created in association with a MOC).

Related

How to delete all entries in Core Data on my phone from Xcode?

I would like to delete all entries in my core data on my iphone some time to time as I run my app.
I just want to check my saving workflow is working but I don't want to do it programatically.
I mean, is there a command/button that from Xcode, sends a message to reset the Core Data entries for the app ran from xcode?
Thanks
I don't think there is any functionality to do that directly from Xcode.
This is what I do:
If you are in the simulator, you can go and look for the SQLite file and open it, then remove the entries that you want. (Remenber that your app must be close if not you will get core data errors due the fact that the managed object context will cache what you removed).
In a device, the faster way to reset a db is directly removing the file.
If your core data code handle the creation of the persistent store if it can't find the file at start. You can get into your phone using any app that you like (ie. imazing.com) and remove the sql file and then start again from scratch.
Hope it helps.
I have this code to clean entities, keeping in mind memory pressure loading only the ObjectId and some other tweaks. Core data sucks when you wants to remove to many objects. A cascade rule for deletions using a 1 to n relationship it's a good solution too.
- (BOOL) resetEntity:(NSString*)entityName
       withPredicate:(NSPredicate*)predicate
inManagedObjectContext:(NSManagedObjectContext*)context
            andError:(NSError **)error
{
    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    [fetchRequest setEntity:[NSEntityDescription entityForName:entityName inManagedObjectContext:context]];
    [fetchRequest setIncludesPropertyValues:NO]; //only fetch the managedObjectID to optimize.
    fetchRequest.resultType = NSManagedObjectIDResultType;
    [fetchRequest setFetchBatchSize:20]; //reduce memory usage
    
    if (predicate)
    {
        [fetchRequest setPredicate:predicate];
    }
    
    NSError * fetchError = nil;
    NSArray * objects = [context executeFetchRequest:fetchRequest error:&fetchError];
    
    if (fetchError)
    {
        DDLogError(#"ERROR: Core Data Reset %# : %#", entityName, fetchError);
        *error = fetchError;
        return NO;
    }
    
    for (NSManagedObjectID * objectID in objects)
    {
        [context deleteObject:[context objectWithID:objectID]];
    }
    
    NSError *saveError = nil;
    [context save:&saveError];
    if (saveError) {
        NSLog(#"ERROR: Core Data Reset %# : %#", entityName, fetchError);
        *error = saveError;
        return NO;
    }
    return YES;
}

RestKit › [RestKit 0.9] Swap core data database at runtime

Le mardi 18 juin 2013 12:50:29 UTC+2, Appsido a écrit :
Hello,
I'm facing an issue trying to create a new persistent store at runtime and use this new persistent store.
To create the new persistent store i use the following snippet
NSURL *modelUrl = [[NSBundle bundleForClass:[self class]] URLForResource:#"AppDataModel" withExtension:#"momd"];
NSManagedObjectModel *managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelUrl];
[[AppDelegate appDelegate] objectManager].objectStore = [RKManagedObjectStore objectStoreWithStoreFilename:#"AppStore"
usingSeedDatabaseName:nil
managedObjectModel: managedObjectModel
delegate:nil];
This works fine and create a new persistent store on the iphone device file system.
Then i create a new object and save it in the persistent store with the following snippet
MyEntity *f = [MyEntity object];
[f setValue:[NSNumber numberWithInt:70] forKey:#"id"];
[f setValue:#"New Family" forKey:#"name"];
NSError *error;
[[f managedObjectContext] save:&error];
if (error) NSLog(#"error > %#", error);
The object is saved in persistent store but not the new created one but the old one.
So is it possible to define multiple persistent store based on the same data model file and swap from one to another at runtime, and keep data in each persistent store instance.
Thank you for your support.
Look at https://github.com/magicalpanda/MagicalRecord
// get the default context
[NSManagedObjectContext MR_defaultContext];
// create the other context
NSManagedObjectContext *myNewContext = [NSManagedObjectContext MR_context];
// set the new default
[NSManagedObjectContext MR_setDefaultContext:myNewContext];
You will need somewhere to strore the contexts to keep swapping between them, maybe a dictionary.
You should at first delete old one persistentStore with this code
[objectManager.objectStore deletePersistantStore];

Can temporary NSManagedObjectID be archived?

For permanent NSManagedObjectID, I know you can archive their URIRepresentation and get back the NSManagedObjectID later using the persistant store.
NSURL *uriRep = [objectId URIRepresentation];
NSPersistentStoreCoordinator *psc = ...
NSManagedObjectContext *context = ...
NSManagedObjectID *myID = [psc managedObjectIDForURIRepresentation:uriRep];
NSManagedObject *myObj = [context objectWithID:myID];
However, what if the ID is temporary? Is there a way to still turn it into NSString or NSData and get it back later, bypassing the persistent store, and use it directly with the context? This could be useful for some temporary objects.

"Dangling reference to an invalid object" error in Core Data with seemingly valid objects

I'm having a really confounding problem using Core Data. In my Core Data store, for an existing Core Data object, I'm checking whether a relationship exists, and if not, I create the object like so (this is a method on AFFingerprintGeneratorOperation):
NSManagedObjectContext *newContext = [[NSManagedObjectContext alloc] init];
[newContext setMergePolicy:NSOverwriteMergePolicy];
NSPersistentStoreCoordinator *sharedStoreCoordinator = [[AFMainController sharedInstance] persistentStoreCoordinator];
[newContext setPersistentStoreCoordinator:sharedStoreCoordinator];
[self setManagedObjectContext:newContext]; // retaining property
[newContext release];
NSEntityDescription *fetchedTagSetEntity = [NSEntityDescription entityForName:#"FetchedTagSet"
inManagedObjectContext:newContext
];
AFTrack *retrievedTrack = (AFTrack *)[newContext objectWithID:[self trackObjectID]];
[self setTrack:retrievedTrack];
if (! retrievedTrack.fetchedTagSet) {
AFFetchedTagSet *newFetchedTagSet = [[AFFetchedTagSet alloc] initWithEntity:fetchedTagSetEntity
insertIntoManagedObjectContext:newContext];
[[retrievedTrack storedTrack] setFetchedTagSet:newFetchedTagSet];
[newFetchedTagSet setStoredTrack:[retrievedTrack storedTrack]];
}
AFTrack, AFFetchedTagSet, and AFStoredTrack are all Core Data objects. AFFetchedTagSet and AFStoredTrack are in an on-disk Core Data store, while AFTrack is in a separate in-memory Core Data store.
Note that since the AFStoredTrack object is in a separate store, I need to fetch it like so (this is a method on AFTrack):
NSManagedObjectContext *objectContext = [self managedObjectContext];
NSPersistentStoreCoordinator *coordinator = [objectContext persistentStoreCoordinator];
NSString *URIString = [self storedTrackObjectIDString];
AFStoredTrack *theStoredTrack = nil;
if (URIString) {
NSURL *objectURL = [NSURL URLWithString:URIString];
NSManagedObjectID *storedTrackObjectID = [coordinator managedObjectIDForURIRepresentation:objectURL];
theStoredTrack = (AFStoredTrack *)[objectContext objectWithID:storedTrackObjectID];
}
return theStoredTrack;
Since this is a method on AFTrack, it simply retrieves the AFTrack's own managed object context, so the code in the first excerpt should always use the exact same managed object context for all operations.
However, in the first excerpt, after calling setFetchedTagSet: with the new object, and attempting to save the Core Data store, I get this error:
"Dangling reference to an invalid object." = "<null>";
NSAffectedObjectsErrorKey = (
"<AFStoredTrack: 0x11550eef0> (entity: StoredTrack; id: 0x101eb57d0 <x-coredata:///StoredTrack/tF7F5568E-2959-4786-B73D-B7AC6586F5B9121> ; data: {\n fetchedTagSet = \"0x11c956b60 <x-coredata:///FetchedTagSet/tF7F5568E-2959-4786-B73D-B7AC6586F5B9124>\";\n fingerprint = \"ASPtPiNHPC7fGSYXTxtfFboMCg7BCxYQ+gZRCL4FWQdzBD8HPw\";\n persistentID = nil;\n status = 3;\n updateAlbumName = nil;\n updateArtistName = nil;\n updateArtwork = nil;\n updateGenre = nil;\n updateLyrics = nil;\n updateReleaseYear = nil;\n updateTrackName = nil;\n})"
);
NSLocalizedDescription = "storedTrack is not valid.";
NSValidationErrorKey = storedTrack;
NSValidationErrorObject = "<AFFetchedTagSet: 0x11c956ac0> (entity: FetchedTagSet; id: 0x11c956b60 <x-coredata:///FetchedTagSet/tF7F5568E-2959-4786-B73D-B7AC6586F5B9124> ; data: {\n ampliFindPUID = nil;\n fingerprint = nil;\n matchAlbum = nil;\n matchLyrics = nil;\n matchTrackName = nil;\n matchTrackNumber = 0;\n storedTrack = \"0x101eb57d0 <x-coredata:///StoredTrack/tF7F5568E-2959-4786-B73D-B7AC6586F5B9121>\";\n})";
NSValidationErrorValue = "<AFStoredTrack: 0x11550eef0> (entity: StoredTrack; id: 0x101eb57d0 <x-coredata:///StoredTrack/tF7F5568E-2959-4786-B73D-B7AC6586F5B9121> ; data: {\n fetchedTagSet = \"0x11c956b60 <x-coredata:///FetchedTagSet/tF7F5568E-2959-4786-B73D-B7AC6586F5B9124>\";\n fingerprint = \"ASPtPiNHPC7fGSYXTxtfFboMCg7BCxYQ+gZRCL4FWQdzBD8HPw\";\n persistentID = nil;\n status = 3;\n updateAlbumName = nil;\n updateArtistName = nil;\n updateArtwork = nil;\n updateGenre = nil;\n updateLyrics = nil;\n updateReleaseYear = nil;\n updateTrackName = nil;\n})";
But both the AFFetchedTagSet and AFStoredTrack objects seem to be valid, as their ids match up, and the object context for these objects still exists and is retained by the AFFingerprintGeneratorOperation object.
I've seen this CoreData: "Dangling reference to an invalid object." error and this http://lists.apple.com/archives/cocoa-dev/2009/Nov/msg00190.html , but neither link seems to help. The former seems to say that a relationship is bad (which I don't think it is), and the latter says to avoid changing relationships in awakeFromFetch, which as far as I can tell I'm not doing.
Any help?
The error code that Core Data was throwing was actually quite correct in this instance, but it was hard to figure out what was going on.
The "fetchedTagSet" property on AFTrack objects was simply a convenience read-only property for the "fetchedTagSet" property on AFStoredTrack, meaning that in order to retrieve the AFFetchedTagSet class, I had to retrieve the AFStoredTrack object first.
The method on AFTrack that was fetching the AFStoredTrack method was using the objectRegisteredForID: method. This doesn't fetch objects if they aren't registered with the managed object context that you're calling the objectRegisteredForID: method on. So, I was retrieving non-existent AFStoredTrack faults, and setting properties on them.
(Note: "aren't registered with the managed object context" does not mean that they don't exist in the Core Data store. It simply means that that specific managed object context does not know about that object yet, because you haven't inserted it or fetched it within that context.)
For some reason, objectRegisteredForID: was returning a blank AFStoredTrack, rather than nil as the documentation states. The effect was that I thought I had a valid AFStoredTrack object, but in reality it was just a placeholder with all the values being nil. Trying to save the object made the context realize that the AFStoredTrack was not valid, producing the error message.
The fix was to change the method on AFTrack (the one that retrieved the AFStoredTrack objects) to use objectWithID: rather than objectRegisteredForID: . As the documentation states, objectWithID: will fetch an object if the context doesn't know about it yet. That returned a valid object, and then everything went fine after that.
My problem was solved using this code:
[[CustomManagedObject managedObjectContext] performBlockAndWait:^{
NSError *error;
if (![[CustomManagedObject managedObjectContext] save:&error])
{
NSLog(#"Error in Saving: %#", [error.userInfo description]);
}
}];
Since you're dealing with objects split across two different stores here, and relationships are not allow to span across different stores, you might need to make sure that the AFFetchedTagSet you're creating is assigned to the same persistent store as the AFStoredTrack object you're associating it with. I'm not sure which store Core Data defaults to when creating a new object, but I could see Core Data throwing a fit when you try to save a context that has a relationship set up that spans across the two stores, so setting the store explicitly surely couldn't hurt. You can do this by calling -[NSManagedObjectContext assignObject:toPersistentStore:] after creating the fetched tag set. Not 100% sure that's the problem, but that's what I'd try first.
As a side note, if you have inverse relationships set up in your Core Data model between AFFetchTagSet and AFStoredTrack, then only one of the two calls from -setFetchedTagSet: and -setStoredTrack: should be necessary, and Core Data should take care of the other one automagically.

NSManagedObjectID into NSData

I found this wonderful NSManagedObjectID. This would be very good for referencing an Entity/NSManagedObject/NSEntityDescription, right?
Let's get an ID from an entity:
NSEntityDescription *entity = [self newEntity];
NSManagedObjectID *objID = [entity objectID];
So... any idea how to get this objID into a string? Or better: NSData. Actually something to be able to save it to the NSUserDefaults. ;-)
Btw: NSFetchRequest doesn't want to work in my case. I use an modified version of this example: answer of an old question.
To get an archived URI corresponding to a NSManagedObject's objectID:
NSManagedObject* myMO;
...
NSURL *uri = [[myMO objectID] URIRepresentation];
NSData *uriData = [NSKeyedArchiver archivedDataWithRootObject:uri];
In order to get back to an instance of the original managed object, you need a CoreData stack with the persistent store holding that instance already added to the NSPersistentStoreCoordinator. Then:
NSData *uriData;
NSPersistentStoreCoordinator *psc;
NSManagedObjectContext *moc; //with moc.persistentStoreCoordinator = psc.
...
NSURL *uri = [NSKeyedUnarchiver unarchiveObjectWithData:uriData];
NSManagedObjectID *moID = [psc managedObjectIDForURIRepresentation:uri];
NSManagedObject *myMO = [moc objectWithID:moID];
From the NSManagedObjectID documentation:
Object IDs can be transformed into a
URI representation which can be
archived and recreated later to refer
back to a given object (using
managedObjectIDForURIRepresentation:
(NSPersistentStoreCoordinator) and
objectWithID:
(NSManagedObjectContext). For example,
the last selected group in an
application could be stored in the
user defaults through the group
object’s ID. You can also use object
ID URI representations to store “weak”
relationships across persistent stores
(where no hard join is possible).
Just turn it into a URL then turn that into a string or a data.
Did you look at URIRepresentation? It's easy to convert an NSURL to an NSString, and that to an NSData.
You don't need to convert the NSURL into an NSString before archiving. Just archive the NSURL.
Edit: I've recently learned that an object's ID can change, such as after a migration. It therefore seems like not a good idea to save an ID to disk expecting to be able to reference the object later.
Here's the cleanest and shortest way I've found to do this currently, using the setURL and getURL methods added in 4.0 to avoid extra calls to NSKeyedUnarchiver and NSKeyedArchiver:
Setter:
+ (void)storeSomeObjectId:(NSManagedObjectID *)objectId
{
[[NSUserDefaults standardUserDefaults] setURL:[objectId URIRepresentation]
forKey:#"someObjectIdKey"];
[[NSUserDefaults standardUserDefaults] synchronize];
}
Getter:
+ (SomeManagedObject *)getObjectByStoredId
{
NSURL *uri = [[NSUserDefaults standardUserDefaults] URLForKey:#"someObjectIdKey"];
NSManagedObjectID *objectId = [self.persistentStoreCoordinator managedObjectIDForURIRepresentation:uri];
SomeManagedObject *object = [self.managedObjectContext objectWithID:objectId];
}
As #preston said, don't save an objectID to disk, instead:
Make a new attribute on your entity called "id"
Make a new attribute on your entitys parent entity called "myEntitysMaxId"
Override your entitys parent implementation "addNewMyEntityObject:"
There, increase "myEntitysMaxId" and set that value as the new entitys "id"
Do as you normally do when you fetch an entity based on its attributes!
Much cleaner and better!

Resources