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!
Related
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).
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];
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.
Apologies if this has been answered before but I can't find a reference. I am trying Cocoa / obj-c for the first time. I am trying to knock up an app which will sync with a remote backup system via http (a la s3) and am stumbling around some fundamental core data issues.
I have created an entity and can invoke this without issues. The problem arrives when I call save on NSManagedObjectContext.
I am not going to include all methods involved in invoking the object context / model as the log output should (I think) verify that it is working as expected.
Best described with code and appropriate log entries.
*First, for illustration, I am invoking the managed object: *
- (NSManagedObjectModel *)managedObjectModel {
if (managedObjectModel != nil) {
return managedObjectModel;
}
managedObjectModel = [[NSManagedObjectModel mergedModelFromBundles:nil] retain];
NSLog(#"The managed object model is defined as follows:\n%#", managedObjectModel);
return managedObjectModel;
}
And the log output from the above NSLog:
2011-09-06 14:31:38.322 TryAgain[18885:a0f] The managed object model is defined as follows:
(<NSManagedObjectModel: 0x2000e2b00>) isEditable 1, entities {
BackupItinerary = "(<NSEntityDescription: 0x20020e9e0>) name BackupItinerary, managedObjectClassName NSManagedObject, renamingIdentifier
BackupItinerary, isAbstract 0, superentity name (null), properties {\n \"file_url\" = \"(<NSAttributeDescription: 0x2000faec0>), name
file_url, isOptional 0, isTransient 0, entity BackupItinerary, renamingIdentifier file_url, validation predicates (\\n), warnings (\\n),
versionHashModifier (null), attributeType 700 , attributeValueClassName NSString, defaultValue (null)\";\n \"last_sync_date\" =
\"(<NSAttributeDescription: 0x2000faf60>), name last_sync_date, isOptional 1, isTransient 0, entity BackupItinerary, renamingIdentifier
last_sync_date, validation predicates (\\n), warnings (\\n), versionHashModifier (null), attributeType 900 , attributeValueClassName
NSDate, defaultValue (null)\";\n}, subentities {\n}, userInfo {\n}, versionHashModifier (null)";
}, fetch request templates {
}
This looks so have been successful. No exceptions thrown, or warnings.
Now, the actual problem arrives when I call save on the object context. I have a NSOpenPanel which allows for picking dir / files to backup (all hooked up and working fine). Upon the user selecting a dir/file I want to set the value, so I:
NSArray *paths = [panel URLs];
NSURL *filePath = [paths objectAtIndex:0];
[directories addObject:filePath];
[directories setArray:[[NSSet setWithArray: directories] allObjects]];
NSEntityDescription *BackupItineraryEntity = [[self.managedObjectModel entitiesByName] objectForKey:#"BackupItinerary"];
NSManagedObject* BackupItinerary = [[NSManagedObject alloc]
initWithEntity:BackupItineraryEntity
insertIntoManagedObjectContext:self.managedObjectContext];
[BackupItinerary setValue:[filePath absoluteString] forKey:#"file_url"];
NSLog(#"entity:\n%#", BackupItinerary);
And the call to NSLog says (having selected /Users/rick/selenium2-webdriver/):
2011-09-06 14:31:38.328 TryAgain[18885:a0f] entity:
<NSManagedObject: 0x200216c80> (entity: BackupItinerary; id: 0x200090860 <x-coredata:///BackupItinerary/t0C005B39-D185-454B-B364-31314EEB10F02> ;
data: {
"file_url" = "file://localhost/Users/rick/selenium2-webdriver/";
"last_sync_date" = nil;
})
So, the file_url seems to be populated then, yes? But when I:
NSError *error;
if (![[self managedObjectContext] save:&error]) {
NSLog(#"Unresolved error %#, %#, %#", error, [error userInfo],[error localizedDescription]);
}
The log says:
2011-09-06 14:31:38.330 TryAgain[18885:a0f] Unresolved error Error Domain=NSCocoaErrorDomain Code=1570 UserInfo=0x2000cc4e0 "file_url is a required
value.", {
NSLocalizedDescription = "file_url is a required value.";
NSValidationErrorKey = "file_url";
NSValidationErrorObject = "<NSManagedObject: 0x20020f660> (entity: BackupItinerary; id: 0x20008faa0 <x-coredata:///BackupItinerary/t0C005B39
D185-454B-B364-31314EEB10F03> ; data: {\n \"file_url\" = nil;\n \"last_sync_date\" = nil;\n})";
}, file_url is a required value.
So basically:
I can, it seems (?), invoke the an entity and set a value on it, but when it comes to saving it the values is not set. The above code is executed inline and I am using garbage collection.
Feels like a total newb school boy error issue but for the life of me I can't see what I am missing having gone over the docs, highlevel tutorials and example code.
Pointers appreciated!
You are looking at two different managed object instances. The instance that has its file_url property set is <NSManagedObject: 0x200216c80> while the one that reports the error is <NSManagedObject: 0x20020f660>.
Somewhere, you are inserting an instance but not giving it a file_url value. In the code given, it might happen if the user selects cancel in the NSOpenPanel.
Judging from the output of your NSLog's, I'd say that BackupItinerary object is different when saving your context. Check this output on different occasions:
When inserting and setting the value:
entity:
<NSManagedObject: 0x200216c80>
When saving:
NSValidationErrorObject = "<NSManagedObject: 0x20020f660>
As you can see the addresses of instances differ, hence you're inserting one, but saving the other instance. Hope this helps.
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.