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.
Related
So I'm trying to pull in data from Parse.com and then add it to a global array to update a table view with. Right now I have:
- (void)loadData {
PFQuery *query = [PFQuery queryWithClassName:#"Event"];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
for (PFObject *object in objects) {
EventObject *thisEvent = [[EventObject alloc] initWithPFObj:object];
[self.events addObject:thisEvent];
}
[self.tableView reloadData];
}];
}
When the tableview tries to reload the data, it finds an object in self.events, but the object's properties are all nil. (I think this has something to do with weak/strong self in an asynchronous block, but I can't figure it out.) How do I get the data to be preserved between this block and the reload?
Since you are using block to fill data on your array by accessing Self, you must use a weakSelf variable to dont loose the reference.
So you must use this weakSelf instead of just Self, on your loadData method:
__weak typeof(self) weakSelf = self;
Also, remember to put your NSMutableArray "events" as Strong, not Weak.
Edit: A brief explanation of the difference between Strong and Weak references: https://stackoverflow.com/a/18344946/2788711
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.
Is it better to create a local object for later use like
NSDictionary *dic = [NSDictionary Dictionary];
or
NSDictionary * dic = nil;
Is it preference thing or is one better then the other?
it's not like 'the one is better', it's like 'the other is bad'.
If you're going to assign a new object to it later, initialize it to nil, else (you leak memory by losing the reference to the first object created by error.) - EDIT: no, you're not leaking memory (either because of the autorelease or the automatic reference counting, but anyway, that's an extra unneeded method call.) That is bad.
If it's a mutable collection, create it before you use it, else it will continue being nil and ignoring essentially all messages sent to it, which is also bad.
Conclusion: it's not a matter of preference - you must think logically and choose whichever is suited for the specific purpose you are using it for.
If you will use that object later, then you should instantiate it with the first option. If you will have to create an object in some if-else block where you will be reinitializing it with some custom values, then the second option is the way to go.
For example the first option:
NSMutableArray *arr = [NSMutableArray array];
for (int i = 0; i < 5; i++) {
[arr addObject:#"string"];
}
or
NSDictionary *dictionary = nil;
BOOL flag;
if (flag) {
dictionary = [NSDictionary dictionaryWithObject:#"string" forKey:#"myKey"];
}
else {
NSArray *objects;
NSArray *keys;
dictionary = [NSDictionary dictionaryWithObjects:objects forKeys:keys];
}
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,
Here's my code of generating data
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
[array initWithCapacity:20];
}
- (IBAction) readlog:(id)sender {
for (int i = 0; i < 20; i++) {
NSDictionary *d = [NSDictionary dictionaryWithContentsOfFile:[path stringByAppendingFormat:#"/%d.log",i]];
[array addObject:d];
}
}
- (IBAction) writelog:(id)sender {
for (int i = 0; i < 20; i++) {
NSMutableDictionary *d = [NSMutableDictionary dictionary];
NSString *name = [NSString stringWithFormat:#"testfile%d", i];
[d setObject:[NSDate date] forKey:#"date"];
[d setObject:[path stringByAppendingFormat:#"/%d.log", i] forKey:#"path"];
[d setObject:name forKey:#"name"];
[d writeToFile:[path stringByAppendingFormat:#"/%d.log", i] atomically:YES];
}
and I bind my tableview column with appdelegate.array with keypath name/path/date
but it doesn't show any data in the array.. is there anything wrong here?
Thanks!
You haven't created an array.
init methods, including NSMutableArray's initWithCapacity:, initialize an existing (freshly-created) instance. You haven't created one, so you're sending that initWithCapacity: message to nil, which means it has no effect.
You need to create the array, then initialize it, then assign it to your array variable, preferably all in the same line.
There's also the issue that your table view will have already asked for the array by the time you receive the applicationDidFinishLaunching: message. You don't have one yet, so it gets nothing; by the time you create one, it has already asked you for it and gotten its answer, and does not know that it should ask again.
Create your array in init or initWithCoder: (I believe you will need the latter if your app delegate is in a nib), and implement and use Key-Value-Coding-compatible accessor methods to fill the array with values. When you send yourself accessor messages, you'll cause KVO notifications that will tip off the table view that it needs to ask for the array again. Assigning directly to the instance variable will not cause this effect.
A couple of other thingsā¦
You have three [path stringByAppendingFormat:#"/%d.log", i] expressions in two different methods. Don't repeat yourself. Move that to a method named something like logFileNameWithLogFileNumber: and send yourself that message to generate the filename. This will make the code both clearer and easier to maintain.
Finally, as a matter of style, you should not use stringByAppendingFormat: or stringWithFormat: to construct paths. Use stringByAppendingPathComponent: (in this case, together with stringWithFormat: to generate the filename). Clarity and pathname-separator-independence are virtues.