PFUser throwing NSInternalInconsistencyException when calling objectForKey after fetchAllIfNeeded - objective-c

I have two additional columns on my PFUser "firstName" and "lastName". They are saving properly; I can see the data in the data browser.
I have another PFObject "class" that has a property with a NSArray of PFUsers. I use the class method +fetchAllIfNeededInBackground:block: on PFObject to fetch the array of PFUsers. In the callback block, I call objectForKey: on each of the PFUsers in the array, but I access them through the owning PFObject.
// invited is the NSArray of PFUsers
self.whoCell.mainLabel.text = [[self.plan.invited objectAtIndex:0]
objectForKey:#"firstName"];
Debugger outputs this at a breakpoint right before the objectForKey call:
(lldb) po self.plan.invited
(NSArray *) $4 = 0x06e62a60 <__NSArrayM 0x6e62a60>(
<PFUser:GCCdPjCU2J> {
firstName = Fake;
lastName = Account;
username = yyajnbafv53qw4yhjm9sfoiis;
}
)
Edit: adding implementation of self.plan.invited because the above is misleading.
- (NSArray*)invited
{
// self.dataSource is a PFObject*
return [self.dataSource objectForKey:INVITED_PROP];
}
Yet when the above call it made to objectForKey: this exception is thrown:
'NSInternalInconsistencyException', reason: 'Key "firstName" has no data. Call fetchIfNeeded before getting its value.'
Edit: Accessing the array of fetchedObjects that is passed to the block callback for +fetchAllIfNeededInBackground doesn't throw, but accessing the actual array that was originally passed to +fetchAllIfNeededInBackground throws.
Calling fetchIfNeeded before the call solves the problem, but why? The data is already there. Do I miss understand +fetchAllIfNeededInBackground in that it DOES NOT updated the PFObject that owns to collection of PFUsers?

I figured out what was happening. Let me explain with code:
PFQuery *query = [PFQuery queryWithClassName:#"TestClass"];
PFObject *testObj = [query getObjectWithId:#"xWMyZ4YEGZ"];
// an array of PFUser "pointers" (pointers in Parse parlance)
NSLog(#"%#", [testObj objectForKey:#"userArrayProp"]);
[PFObject fetchAll:[testObj objectForKey:#"userArrayProp"]];
// now userArrayProp contains fully fetched PFObjects
NSLog(#"%#", [testObj objectForKey:#"userArrayProp"]);
For me, after a certain amount of time userArrayProp would revert to an array of "pointers" and this mystified me. My problem was that calling refresh on a PFObject will revert a fetched array BACK to an array of pointers. Like this:
PFQuery *query = [PFQuery queryWithClassName:#"TestClass"];
PFObject *testObj = [query getObjectWithId:#"xWMyZ4YEGZ"];
// an array of PFUser "pointers" (pointers in Parse parlance)
NSLog(#"%#", [testObj objectForKey:#"userArrayProp"]);
[PFObject fetchAll:[testObj objectForKey:#"userArrayProp"]];
// now userArrayProp contains fully fetched PFObjects
NSLog(#"%#", [testObj objectForKey:#"userArrayProp"]);
[testObj refresh];
// now userArrayProp contains pointers again :(
NSLog(#"%#", [testObj objectForKey:#"userArrayProp"]);
Wish it said that [PFObject refresh] did that in the documentation....

What you are describing should work fine. fetchAllIfNeeded updates the objects in the array themselves, so it shouldn't matter how you access them. You say you are accessing them through the parent PFObject, but your debugger output is showing access through the array directly. Is it possible the array isn't pointing to the current PFObject member?
One thing you could try while debugging is calling isDataAvailable on the instances of PFUser, both after the fetchAllIfNeeded, and right before you access their first and last names. After the call to fetchAllIfNeeded, isDataAvailable should return YES for every element of the array. If it still returns YES when you access the names, they should not give this error.
Otherwise, if you can provide a minimal code sample that reproduces the problem, I'd be glad to debug it further.
Thanks,

Related

PFQuery includeKey on array of dictionaries.

I have a parse class with a NSArray column, it is an array of dictionaries, and inside that dictionaryI have a key with a pointer to another class. I'm trying to use includeKey but it is not working. I'm hoping to get the objects inside pointers without calling fetch, since there are many, the requests could go up easily.
I'm using: [query includeKey:#"ingredients.item"]; Ingredients is an NSArray of dictionaries, it is a column in the main object. item is a key in the dictionary that stores pointers.
[{"item":{"__type":"Pointer","className":"NutricaoCatalogo","objectId":"aTnHD5ttUH"},"ingredientTitle":"Clara De Ovo Natural","portionIndex":"1","portionQuantity":200},
Try this it may help you
PFQuery *query = [PFQuery queryWithClassName:#"ingredients"];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error && objects) {
for(int i = 0; i < objects.count; i++) {
PFObject *object = [objects objectAtIndex:0];
PFObject *pointer = [[object valueForKey:#"item"] valueForKey:#"__type"];
PFObject *obj = [PFQuery getObjectOfClass:#"<classname of that pointer points to>" objectId:(NSString *)pointer];
//obj is an object of that pointer so you can manupulate accordingly
//Note: getObjectOfClass: objectId: method run on main thread
}
}
}];
include only works on pointers. It doesn't even work on an array of pointers.
One point I want to make first is that unless you have a ton of active users, you're not going to hit the mark for free requests any time soon. If you are, your app should be profitable enough that it doesn't matter.
My best solution would be to call a background job that performs a query.each() and pulls all of the object ids that you'd like to get, then returns the array of objectIds. Then, you can query for all the objects with ids that are containedIn that array.

Core data find-or-create most efficient way

I have around 10000 objects of entity 'Message'. When I add a new 'Message' i want to first see whether it exists - and if it does just update it's data, but if it doesn't to create it.
Right now the "find-or-create" algorithm works with by saving all of the Message objects 'objectID' in one array and then filtering through them and getting the messages with existingObjectWithID:error:
This works fine but in my case when I fetch an 'Message' using existingObjectWithID: and then try to set and save a property by setting the property of the 'Message' object and calling save: on it's context it doesn't saves it properly. Has anyone come across a problem like this?
Is there a more efficient way to make find-or-create algorithm?
First, Message is a "bad" name for a CoreData entity as apple use it internally and it cause problems later in development.
You can read a little more about it HERE
I've noticed that all suggested solutions here use an array or a fetch request.
You might want to consider a dictionary based solution ...
In a single threaded/context application this is accomplished without too much of a burden by adding to cache (dictionary) the newly inserted objects (of type Message) and pre-populating the cache with existing object ids and keys mapping.
Consider this interface:
#interface UniquenessEnforcer : NSObject
#property (readonly,nonatomic,strong) NSPersistentStoreCoordinator* coordinator;
#property (readonly,nonatomic,strong) NSEntityDescription* entity;
#property (readonly,nonatomic,strong) NSString* keyProperty;
#property (nonatomic,readonly,strong) NSError* error;
- (instancetype) initWithEntity:(NSEntityDescription *)entity
keyProperty:(NSString*)keyProperty
coordinator:(NSPersistentStoreCoordinator*)coordinator;
- (NSArray*) existingObjectIDsForKeys:(NSArray*)keys;
- (void) unregisterKeys:(NSArray*)keys;
- (void) registerObjects:(NSArray*)objects;//objects must have permanent objectIDs
- (NSArray*) findOrCreate:(NSArray*)keys
context:(NSManagedObjectContext*)context
error:(NSError* __autoreleasing*)error;
#end
flow:
1) on application start, allocate a "uniqueness enforcer" and populate your cache:
//private method of uniqueness enforcer
- (void) populateCache
{
NSManagedObjectContext* context = [[NSManagedObjectContext alloc] init];
context.persistentStoreCoordinator = self.coordinator;
NSFetchRequest* r = [NSFetchRequest fetchRequestWithEntityName:self.entity.name];
[r setResultType:NSDictionaryResultType];
NSExpressionDescription* objectIdDesc = [NSExpressionDescription new];
objectIdDesc.name = #"objectID";
objectIdDesc.expression = [NSExpression expressionForEvaluatedObject];
objectIdDesc.expressionResultType = NSObjectIDAttributeType;
r.propertiesToFetch = #[self.keyProperty,objectIdDesc];
NSError* error = nil;
NSArray* results = [context executeFetchRequest:r error:&error];
self.error = error;
if (results) {
for (NSDictionary* dict in results) {
_cache[dict[self.keyProperty]] = dict[#"objectID"];
}
} else {
_cache = nil;
}
}
2) when you need to test existence simply use:
- (NSArray*) existingObjectIDsForKeys:(NSArray *)keys
{
return [_cache objectsForKeys:keys notFoundMarker:[NSNull null]];
}
3) when you like to actually get objects and create missing ones:
- (NSArray*) findOrCreate:(NSArray*)keys
context:(NSManagedObjectContext*)context
error:(NSError* __autoreleasing*)error
{
NSMutableArray* fullList = [[NSMutableArray alloc] initWithCapacity:[keys count]];
NSMutableArray* needFetch = [[NSMutableArray alloc] initWithCapacity:[keys count]];
NSManagedObject* object = nil;
for (id<NSCopying> key in keys) {
NSManagedObjectID* oID = _cache[key];
if (oID) {
object = [context objectWithID:oID];
if ([object isFault]) {
[needFetch addObject:oID];
}
} else {
object = [NSEntityDescription insertNewObjectForEntityForName:self.entity.name
inManagedObjectContext:context];
[object setValue:key forKey:self.keyProperty];
}
[fullList addObject:object];
}
if ([needFetch count]) {
NSFetchRequest* r = [NSFetchRequest fetchRequestWithEntityName:self.entity.name];
r.predicate = [NSPredicate predicateWithFormat:#"SELF IN %#",needFetch];
if([context executeFetchRequest:r error:error] == nil) {//load the missing faults from store
fullList = nil;
}
}
return fullList;
}
In this implementation you need to keep track of objects deletion/creation yourself.
You can use the register/unregister methods (trivial implementation) for this after a successful save.
You could make this a bit more automatic by hooking into the context "save" notification and updating the cache with relevant changes.
The multi-threaded case is much more complex (same interface but different implementation altogether when taking performance into account).
For instance, you must make your enforcer save new items (to the store) before returning them to the requesting context as they don't have permanent IDs otherwise, and even if you call "obtain permanent IDs" the requesting context might not save eventually.
you will also need to use a dispatch queue of some sort (parallel or serial) to access your cache dictionary.
Some math:
Given:
10K (10*1024) unique key objects
average key length of 256[byte]
objectID length of 128[byte]
we are looking at:
10K*(256+128) =~ 4[MB] of memory
This might be a high estimate, but you should take this into account ...
Ok, many things can go wrong here this is how to:
Create NSManagedObjectContext -> MOC
Create NSFetchRequest with the right entity
Create the NSPredicate and attache it to the fetch request
execute fetch request on newly created context
fetch request will return an array of objects matching the predicate
(you should have only one object in that array if your ids are distinct)
cast first element of an array to NSManagedObject
change its property
save context
The most important thing of all is that you use the same context for fetching and saving, and u must do it in the same thread cause MOC is not thread safe and that is the most common error that people do
Currently you say you maintain an array of `objectID's. When you need to you:
filter through them and get the messages with existingObjectWithID:error:
and after this you need to check if the message you got back:
exists
matches the one you want
This is very inefficient. It is inefficient because you are always fetching objects back from the data store into memory. You are also doing it individually (not batching). This is basically the slowest way you could possibly do it.
Why changes to that object aren't saved properly isn't clear. You should get an error of some kind. But, you should really change your search approach:
Instead of looping and loading, use a single fetch request with a predicate:
NSFetchRequest *request = ...;
NSPredicate *filterPredicate = [NSPredicate predicateWithFormat:#"XXX == %#", YYY];
[request setPredicate:filterPredicate];
[request setFetchLimit:1];
where XXX is the name of the attribute in the message to test, and YYY is the value to test it against.
When you execute this fetch on the MOC you should get one or zero responses. If you get zero, create + insert a new message and save the MOC. If you get one, update it and save the MOC.

Trying to add entries in NSArray to NSDictionary, but values in NSDictionary are getting deallocated

I have three Deck Objects which are mostly just wrappers around NSArray objects containing Card objects. Initially, all the Card objects are in deck1, and eventually move through deck2 and deck3.
I also have an NSDictionary object that maps NSString objects to Card objects. I use the cards in deck1 to build this lookup table at the beginning of the game like this...
-(NSDictionary *)buildLookupTable {
NSMutableDictionary *lookup = [[NSMutableDictionary alloc] init];
for (Card *card in self.cards) {
NSString *lookupCode = [self buildCode:card];
[lookup setObject:card forKey:lookupCode];
}
return lookup;
}
I then pass the NSDictionary and the Decks to a layer object, but when I attempt to lookup a given Card based on a NSString, I get "-[CFString hash]: message sent to deallocated instance", but I can easily find the Card I'm looking for, not deallocated, in deck1 or deck2.
The NSString I'm using as a key to retrieve the desired value isn't deallocated, nor is the NSDictionary itself. I have even iterated over the return from the NSDictionary object's allValues method, and none of those are deallocated either.
What other deallocated objects could there be?
Edit-version2:
I've narrowed it down a bit.
In the lookup code this works
NSString *key = #"4-0"; //(NSString *)sprite.userData;
return [self.cardLookup objectForKey:key];
but this doesn't
NSString *key = (NSString *)sprite.userData; // value is #"4-0"
return [self.cardLookup objectForKey:key];
In the debugger sprite.userData looks fine.
sprite.userData is defined in the Card class buildSprite method as...
sprite.userData = [NSString stringWithFormat:#"%i-%i",self.x, self.y];
It looks like calling copyWithZone on key doesn't work (like it would for any other NSString).
Found the issue!
The problem was with the sprite.userData object and how it was created.
sprite.userData = [NSString stringWithFormat:#"%i-%i",self.x, self.y];
Evidently sprite.userData was getting released (thought the Xcode debugger seemed to know what it was, possibly because I had zombie objects enabled). The correct version is...
sprite.userData = [[NSString stringWithFormat:#"%i-%i",self.x, self.y] retain];

NSArray initWithObjects only initializing with some of the objects

I've encountered a weird issue thats causing me quite the headache. I am initializing an NSArray object using initWithObjects. I'm passing in 7 objects but immediately afterwords, if I log the count of the array, I only have a count of 3. Has anyone else seen this? I've used this method countless times with no problem before and I can't see what I'm doing wrong. The code is below:
-(DMORecipe *) saveRecipe:(NSNumber *)recipeID recipeTitle:(NSString *)title recipeDescription:(NSString *)description pictureFile:(NSString *)picFile preparationTime:(NSString *)prepTime cookingTime:(NSString *)cookTime ovenTemperature:(NSString *)ovenTemp {
NSArray *newRow = [[NSArray alloc] initWithObjects:recipeID,title, description, picFile, prepTime, cookTime, ovenTemp, nil];
NSLog(#"Before update, the number of args is %i", [newRow count]);
}
Do I have a type-o somewhere that I'm missing? You can see I'm passing in 7 objects to the array initializer but the NSLog method shows [newRow count] = 3.
If any of the object passed in is nil, the rest of the argument will be ignored.
In this case, it seems that picFile is nil.

Retain/Release through intermediary method

I think I understand retain/release in objective-C for the most part. However, I have a specific case I am unsure about. Here is an example:
+ (NSString *)getPlayerNameByIndex:(NSInteger)globalIndex:(ABAddressBookRef)addressBook
{
...
Player *player = [PlayerHelper loadPlayer:globalIndex];
NSString *name = [PlayerHelper getPlayerName:player :addressBook];
[player release];
// 'retain' here?
return name;
}
+ (NSString *)getPlayerName:(Player *)player:(ABAddressBookRef)addressBook
{
...
NSString *name = [[[NSString alloc] initWithString:player.nickname] autorelease];
return name;
}
So then I call...
NSString *name = [PlayerHelper getPlayerNameByIndex:index:addressBook];
// name is 'autorelease'?
What I saw on random occasions is that the view sometimes shows the 'name' field as empty when it populates the table after coming back from another view. This could be another issue but I want to be sure of my use of 'autorelease'.
The core of my question is the use of 'autorelease' in getPlayerName. Does the 'autorelease' state of being get passed through method getPlayerNameByIndex to the caller?
Or, do I have to call 'retain' in the intermediary method? I am thinking 'autorelease' may be releasing in method getPlayerNameByIndex.
Hopefully my question is clear. Any help is appreciated.
Update: Some more info for clarification...
NSError *error = nil;
Player *player = nil;
NSArray *array = [appDelegate.managedObjectContext executeFetchRequest:request error:&error];
if ([array count] == 1)
{
player = [array objectAtIndex:0];
[player retain];
}
This is essentially the "loadPlayer" method which loads info from core data. From the answers it sounds like I do not need to call [player retain], since it is an autorelated object, and I can simply return "player" and use it? Thanks for the responses!
The core of my question is the use of 'autorelease' in getPlayerName. Does the 'autorelease' state of being get passed through method getPlayerNameByIndex to the caller?
The answer is yes.
Or, do I have to call 'retain' in the intermediary method?
whether you want to call retain depends on the semantics of your method.
In Obj-C/Cocoa, the following convention applies: a method whose name begins with “alloc” or “new” or contains “copy” will return a retained object; otherwise you can expect to get an autoreleased object, then it is the caller responsibility to retain it according to its needs.
I am thinking 'autorelease' may be releasing in method getPlayerNameByIndex.
autoreleased objects are released at the next point in time when the autorelease pool is drained; this is usually associated to going back to the main loop (though, no details are available about this); so you can be pretty sure that auto-releasing does not kick in in getPlayerNameByIndex...
Hope this helps clarifying the issue...
In getPlayerNameByIndex The line:
[player release];
is wong, remove it. You did not obtain ownership. Ownership is ob gained by calling a method with alloc or the method names starts with new, copy or an explicit retain. (NARC).
You do not need to release player because you did not obtain ownership, see above rule.
In getPlayerName:
can be simplified to:
return player.nickname;
The method name can be simplifies to:
+ (NSString *)getPlayerName:(Player *)player