How to retrieve stored reference to an NSManagedObject subclass? - iphone

I have a NSManagedObject subclass named Tour. I stored the reference to it using this code:
prefs = [NSUserDefaults standardUserDefaults];
NSURL *myURL = [[myTour objectID] URIRepresentation];
NSData *uriData = [NSKeyedArchiver archivedDataWithRootObject:myURL];
[prefs setObject:uriData forKey:#"tour"];
Now I want to retrieve it. I tried using:
NSData *myData = [prefs objectForKey:#"tour"];
NSURL *myURL = [NSKeyedUnarchiver unarchiveObjectWithData:myData];
TourAppDelegate *appDelegate = (TourAppDelegate *)[[UIApplication sharedApplication] delegate];
NSManagedObjectID *myID = [appDelegate.persistentStoreCoordinator managedObjectIDForURIRepresentation:myURL];
if (myID)
{
Tour *tempObject = [appDelegate.managedObjectContext objectWithID:myID]; //WARNING
tour = tempObject;
}
if (tour) //instruction...
But it's giving me this warning "Incompatible Objective-c types. Initializing 'struct NSManagedObject *', expected 'struct Tour *'
Plus, when executing, it's giving me this: Terminating app due to uncaught exception 'NSObjectInaccessibleException', reason: 'CoreData could not fulfill a fault for '0x5001eb0
How can I solve this?

Regarding the warning, did you try to force type casting?
Tour *tempObject = (Tour *) [appDelegate.managedObjectContext objectWithID:myID];
The problem related to NSObjectInaccessibleException is solved in the link St3fan posted :)
PS: Remember that a subclass of nsmanagedobject is still a nsmanagedobject!

This is a great article about storing and retrieving references to objects.
http://cocoawithlove.com/2008/08/safely-fetching-nsmanagedobject-by-uri.html

Looks like the URI you have is not registered in the context.
From the docs:
If the object is not registered in the context, it may be fetched or returned as a fault. This method always returns an object. The data in the persistent store represented by objectID is assumed to exist—if it does not, the returned object throws an exception when you access any property (that is, when the fault is fired). The benefit of this behavior is that it allows you to create and use faults, then create the underlying rows later or in a separate context.
objectRegisteredForID: will return nil if you want to gracefully fail from this condition

Related

Core Data get back subclassed NSManagedObject from its objectID

I have 3 entities that I generated with MOGenerator, I'd like to be able to get one of them back from their objectID
I tried this :
- (void)aMethod: (SpecialEntity1ID *)entityID
{
//This is a method from MagicalRecord but it doesn't matter(I think...).
NSManagedObjectContext *context = [NSManagedObjectContext MR_contextWithParent:[NSManagedObjectContext MR_defaultContext]];
SpecialEntity1 *entity1 = [context objectRegisteredForID:entityID]
//But this returns an NSManagedObject so it doesn't work...
}
Could someone help me get this object back with its ID ?
Since I don't know how to do it with the ID I'm currently working around it by making a method with an NSStringas a paramater instead of SecialEntity1ID that defines one of the attribute of this object (and is unique) and fetching the object.
I think getting back with his ID is better so any idea ?
You want to use existingObjectWithID:error: method of your NSManagedObjectContext and typecast the return type if you are 100% sure what it will be. I'd keep it generic i.e. let it return an NSManagedObject and then test its class elsewhere if you want to determine whether it belongs to a particular class.
- (Object*)retrieveObjectWithID:(ObjectID*)theID
{
NSError *error = nil;
Object *theObject = (Object*)[[NSManagedObjectContext contextForCurrentThread] existingObjectWithID:theID error:&error];
if (error)
NSLog (#"Error retrieving object with ID %#: %#", theID, error);
return theObject;
}

memory management in class method in iOS

I have problems with memory in my class methods.
I have created a class method which will fetch records from core data and return a NSArray.
These are the problem i face:
sometimes the data is returned properly, it works fine.
sometimes it returns a CFArray
a.how to deal with with type of array??
b.what does this type of array mean??
sometimes the array becomes a invalid object when returned to the class which called the method
But in all the ways NSArray inside the method has data. Why does it react in different ways every time? Is there any way to manage this problem?
Code snippet used:
+(NSArray *)retrieveEvents
{
NSArray *arrData;
NSError *error;
NSFetchRequest *fetch = [APPDEL.managedObjectModel fetchRequestTemplateForName:#"fetchEvents"];
arrData = [NSArray arrayWithArray:[APPDEL.managedObjectContext executeFetchRequest:fetch error:&error]];
return arrData;
}

NSdata length crash on device

While running this code:
NSData *archivedSavedData = [[NSData alloc] init];
archivedSavedData = [defaults objectForKey:#"listOfAccessNumbers"];
NSLog(#"archivedSavedData length is %d", [archivedSavedData length] );
I am getting this crash error (last line) only when running on a device that is connected:
[__NSCFArray length]: unrecognized selector sent to instance 0x2398a0
2012-03-13 20:25:33.088[7301:707] * Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSCFArray length]: unrecognized selector sent to instance 0x2398a0'
* First throw call stack:
(0x34dbc88f 0x361e3259 0x34dbfa9b 0x34dbe915 0x34d19650 0xccb1b 0x31e13e33 0x31e38629 0x31dfcd7d 0x31ebf4dd 0x31e0555d 0x31e05579 0x31e0540b 0x31e053e7 0xcfedf 0x31e12e53 0x31e0c985 0x31ddac6b 0x31dda70f 0x31dda0e3 0x3600f22b 0x34d90523 0x34d904c5 0x34d8f313 0x34d124a5 0x34d1236d 0x31e0ba13 0x31e08e7d 0xcfd39 0xcbe28)
terminate called throwing an exception
This doesn't happen when running on the simulator or directly on the device with a distribution profile (through testflight for example).
Does anyone know how such a behavior could happen only in this case?
Thanks.
UPDATE: when trying to replace length with count I get this complication error: "No visible #interface for 'NSData' declares the selector 'count'"
UPDATE2: I understand that it should be an NSArray rather than an NSData, but my problem is that I did store archived NSData cause my array consists of custom objects, so I had to archived this data into NSData format when saving in NSUserDefault. How else should I approach that otherwise?
Thats how I store the data:
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:array];
[defaults setObject:data forKey:#"listOfAccessNumbers"];
array is an array of custom objects of the form of:
#interface NumberDataObj : NSObject {
NSString *inputName;
NSString *inputNum;
}
The error message says:
-[__NSCFArray length]: unrecognized selector sent to instance
That means that archivedSavedData is an array and that it doesn't (obviously) respond to length so you should declare archivedSavedData as an array and use count instead.
NSArray *archivedSavedData = [defaults objectForKey:#"listOfAccessNumbers"];
NSLog(#"archivedSavedData length is %d", [archivedSavedData count]);
Now, as to why this doesn't happen when running on the simulator, my guess is that your test scenarios don't make this part of the code get called.
EDIT
If you want to retrieve the data as NSData then use the method dataForKey:
NSData *archivedSavedData = [defaults dataForKey:#"listOfAccessNumbers"];
NSLog(#"archivedSavedData length is %d", [archivedSavedData length]);
The documentation says for dataForKey:
Return Value
The data object associated with the specified key, or nil if the key does not exist or its value is not an NSData object.
and for arrayForkey:
Return Value
The array associated with the specified key, or nil if the key does not exist or its value is not an NSArray object.
So aways use the appropriate method when you know the type of the data to avoid problems like this.
You have two problems as the code is written:
1) You allocate a variable called archivedSavedData that you reassign on the following line without releasing. This is okay if you are working with ARC, but the first line would then be unnecessary.
2) The second problem is that the object corresponding to the key #"listOfAccessNumbers" stored in your defaults object is of type NSArray. NSArray responds to the selector count, not length. Maybe you should look more closely at this object and recode accordingly.
Hope this helps :)
It's because [defaults objectForKey:#"listOfAccessNumbers"] returns an NSArray, not an NSData object.

iphone, Saving data in to-many relationship, Core Data

I'm working on a small iphone app using Core Data, where I got a Person and an Image entities. The relationship between Person and Image is to-many. One person to many images.
Problem happens at save method. During saving first I need 'ADD' more than one image data, that I got from ImagePickerViewController, to Person and then 'SAVE' the Person entity to actual DB. ((in the same method))
if (managedObjectContext == nil)
{
managedObjectContext = [(CoreDataCombine01AppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
}
//???: how to init _PersonImage?
_PersonImage = (PersonImage *)[NSEntityDescription insertNewObjectForEntityForName:#"PersonImage" inManagedObjectContext:managedObjectContext];
//_PersonImage = [_PersonImage init];
//_PersonImage = [[_PersonImage alloc] init];
_PersonImage.originalImage = imageData;
managedObjectContext = nil;
[managedObjectContext release];
.
if (managedObjectContext == nil)
{
managedObjectContext = [(CoreDataCombine01AppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
}
person = (Person *)[NSEntityDescription insertNewObjectForEntityForName:#"Person" inManagedObjectContext:managedObjectContext];
[person addPersonToPersonImageObject:_PersonImage];//runTime error
[_PersonImage release];
.
NSError *error = nil;
if (![person.managedObjectContext save:&error]) {
// Handle error
NSLog(#"Unresolved error at save %#, %#", error, [error userInfo]);
exit(-1); // Fail
}
then I got error saying:
"-[NSConcreteMutableData CGImage]: unrecognized selector sent to instance"
and:
"*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[NSConcreteMutableData CGImage]: unrecognized selector sent to instance".
I guessed the error came from double use of NSEntityDescription from one managedObjectContext. So I just init Person class, that derived automatically from data model and manually imported, like the commented line instead of using managedObjectContext.
It doesn't give any error but give me runtime error when I hit my save button.
When I was using one-to-one relationship there was no problem with saving so I guess using managedObjectContext for Person is right way. However, once I use to-many relationship I need save Image data to PersonImage entity. And the entity has to be initialized in a way or another.
What am I missing?
Alright.
I've had a look at the code you posted on GitHub and found a few issues with your DetailView controller, which I am outlining below.
Firstly, you were not passing your managed object context to the detail view properly, meaning that when you were trying to save your objects there was no context for them to be saved from. Think of the Managed Object Context as a "draft" of your persistent store. Any changes you make to an NSManagedObject will be tracked and kept on your context until you persist them with the [managedObjectContext save] command.
So just to be clear, you are creating your context in your AppDelegate, then you passed a reference to it to your RootViewController with rootViewController.managedObjectContext = self.managedObjectContext
From the RootViewController, you need to pass the managedObjectContext down to the DetailView using the same technique. So in your tableView:didSelectRowAtIndexPath: method, you should pass a reference to the context so any changes you make on that view will be tracked and can eventually be persisted:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
DetailView *detailView = [[DetailView alloc] initWithNibName:#"DetailView" bundle:nil];
detailView.event = (Event *)[[self fetchedResultsController] objectAtIndexPath:indexPath];
detailView.managedObjectContext = self.managedObjectContext;
// ...
[self.navigationController pushViewController:detailView animated:YES];
[detailView release];
}
I've also updated all the other references to managedObjectContext where you were instantiating a new context object to simply point to self.managedObjectContext,i.e:
JustString *_justString = [NSEntityDescription insertNewObjectForEntityForName:#"JustString" inManagedObjectContext:self.managedObjectContext];
Once that's out of the way, there is only one more thing that was preventing you from saving the image object properly, which TechZen touched on above.
In your DetailView controller, you have a imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info{ method where you transform the UIImage into NSData and then assign it to your Image entity instance.
The problem with that is that you already have a transformer method in your object model (see Event.m) called -(id)transformedValue:(id)value.
So basically you were trying to transform the UIImage into NSData and then passing NSData to the entity, which was actually expecting an UIImage. In this case, I'd recommend that you let your object model deal with the data transformation so in your DetailView controller, comment out the UIImage to NSData transformer code, and pass the image directly to your managed object:
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info{
UIImage *selectedImage = [info objectForKey:#"UIImagePickerControllerOriginalImage"];
// Transform the image to NSData
// ImageToDataTransformer *transformer = [[[ImageToDataTransformer alloc] init] autorelease];
// NSData *imageData = [transformer transformedValue:selectedImage];
Image *_image = [NSEntityDescription insertNewObjectForEntityForName:#"Image" inManagedObjectContext:self.managedObjectContext];
_image.justImage = selectedImage;
[event addEventToImageObject:_image];
[picker dismissModalViewControllerAnimated:YES];
}
After that, you should be good to go so give it a try and let us know if it solves your problems.
CoreData can have a bit of a steep learning curve but once you 'get it' you will realise it's one of the most beautifully crafted APIs available in iOS development.
Good luck with it and let us know how you go!
Rog
The error message you are getting is caused by you confusing an NSData instance for an UIImage instance.
Like NSString, NSArray and similar core classes, NSData is a class cluster instead of single class. NSConcreteMutableData is one of the classes in the cluster.
Your problem arises because you are trying to send the message CGImage to an NSData instance which of course does not have a CGImage method.
Since you have to use a value transformer to store an image in a Core Data attribute, I would suggest looking at you value transformer and/or the method where you assign the image.
Looking at your code, I suspect you have the Person.originalImageset as a transformable attribute but you attempt to assign an NSData object to it. When the value transformer attempts to transform the NSData instance while thinking it is UIImage, it generates the error.

[NSCFString stringValue]: unrecognized selector sent to instance

I'm using this code to query core data and return the value of key, I store the value like this :
NSString *newName= #"test";
[newShot setValue:newName forKey:#"shotNumber"];
and I query like this :
NSManagedObject *mo = [items objectAtIndex:0]; // assuming that array is not empty
NSString *value = [[mo valueForKey:#"shotNumber"] stringValue];
NSLog(#"Value : %#",value);
I'm crashing with this message though :
[NSCFString stringValue]: unrecognized selector sent to instance,
does anyone know where that would be coming from ?
newName (#"test") is already an NSString. There is no need to call -stringValue to convert it to a string.
NSString *value = [mo valueForKey:#"shotNumber"];
I often times add a category for NSString to handle this:
#interface NSString(JB)
-(NSString *) stringValue;
#end
#implementation NSString(JB)
-(NSString *) stringValue {
return self;
}
#end
You can add a similar category to other classes that you want to respond this way.
[mo valueForKey: #"shotNumber"] is returning a string and NSString (of which NSCFString is an implementation detail) do not implement a stringValue method.
Given that NSNumber does implement stringValue, I'd bet you put an NSString into mo when you thought you were putting in an NSNumber.
The value for the key #"shotNumber" is probably of type NSString which is just a wrapper for NSCFString. What you need to do, is, instead of stringValue, use the description method.
Note that you could also get this problem if you are trying to access a string property on an object that you think is something else, but is actually a string.
In my case I thought my Hydration object was in fact a hydration, but checking its class via isKindOfClass I found that it was an NSString and realized that I had incorrectly cast it as a Hydration object and that my problem lied further up the chain.