Context: I would like my game app to work completely offline so users don't need an internet connection to start playing (certain features will be disabled without connectivity). To this end I've exported a PFObject subclass of static data from parse.com that I'd like to include in my app bundle so users don't need to download it from parse.com.
Say my PFObject subclass of static data is Foo, and I have another PFObject subclass called Bar with a pointer to Foo. I run all the following in Airplane Mode:
Foo *foo = [Foo objectWithoutDataWithObjectId:#"someObjectIdFromServer"];
foo.someKey = #"someValue";
Bar *bar = [Bar object];
bar.foo = foo;
[bar pinInBackgroundWithBlock:^(BOOL succeeded, NSError * _Nullable error) {
if (succeeded && !error) {
// Now I query from the local datastore:
PFQuery *query = [Bar query];
[query includeKey:#"foo"];
[query fromLocalDatastore];
[query findObjectsInBackgroundWithBlock:^(NSArray * _Nullable objects, NSError * _Nullable error) {
if (!objects || error) {
// error
} else {
Bar *bar = [objects firstObject];
NSLog(#"bar.foo: %#", bar.foo); // this prints the object out fine
NSLog(#"bar.foo.isDataAvailable: %d", bar.foo.isDataAvailable); // this is 0, even though I called [query includeKey:#"foo"];
NSLog(#"bar.foo.someKey: %#", bar.foo.someKey); // this silently doesn't return. No exception raised or nil value, execution just stops here
NSLog(#"more logging"); // this doesn't print but program execution continues
[bar.foo fetchFromLocalDatastoreInBackground]; // trying this just in case
NSLog(#"bar.foo.isDataAvailable: %d", bar.foo.isDataAvailable); // still 0 even with fetch
}
}];
}
}];
Any idea why bar.foo.isDataAvailable is 0, even though I called [query includeKey:#"foo"] and even fetch? My understanding is that objectWithoutDataWithObjectId can be used to create local copies of objects in the parse cloud that can then be used in pointer relationships. Am I misunderstanding what can be done with these objects when only using the local datastore (i.e. in airplane mode)?
Also I've tried the same thing without using objectWithoutDataWithObjectId (instead doing [Foo object]) and that works fine. I can potentially use this in a workaround (along with some Cloud Code to ensure uniqueness on my static data), but I'd prefer not to since that counts towards my Cloud Data quota.
Any feedback is very appreciated!
Related
I am facing an issue when trying to update data in Parse.
Here is my code that i am using.
PFQuery *query = [PFQuery queryWithClassName:#"GameScore"];
[query whereKey:#"name" equalTo:#"Username"];
[query getFirstObjectInBackgroundWithBlock:^(PFObject *item, NSError *error)
{
if (!error)
{
[item setObject:#500 forKey:"Score"];
}
[item saveInBackgroundWithBlock:^(BOOL succeeded, NSError *itemError)
{
if (!error)
{
NSLog(#"Score is updated");
}
else
{
NSLog(#"Error in updating: %#",error.description);
}
}];
}
else
{
NSLog(#"Error in getting Score: %#",error.description);
}
}];
This code works only when i create a new PFObject and then try to update it.
But,when i exit my app and then try to update the score again,i am unable to update the data.It throws this error..
Error in getting Score: Error Domain=Parse Code=101 "No results matched the query." UserInfo={error=No results matched the query., NSLocalizedDescription=No results matched the query., code=101}
It works again,if i create a new PFObject.
Please help,i am new to Parse and am still try to understand it.
Thank you.
You need to use findObjectsInBackgroundWithBlock instead of getFirstObjectInBackgroundWithBlock as the latter can only be used if there's at least 1 object.
Reference - Parse Questions
You could also use the PFQuery's count method. If the count is >= 1, use getFirstObjectInBackgroundWithBlock, otherwise display a message / handle that case however you'd like.
Other options include storing the objectId of the GameScore object associated with a player on their user object, creating an object without data using the objectId, then fetching it. Or simply use a pointer, but that can do weird things when saving / querying / fetching.
I am testing out Parse localDatastore and am struggling with refreshing the local datastore after a new server PFQuery.
The PFQuery works fine and seems to pin the array to the local datastore just fine. When I change the contents of the array on the server, the server PFQuery pulls down the updated array, but the local datastore doesn't seem to update:
- (void)viewDidLoad {
[super viewDidLoad];
// Query Parse
PFQuery *query = [PFQuery queryWithClassName:#"contacts"];
NSArray *objects = [query findObjects];
[PFObject pinAllInBackground:objects block:^(BOOL succeeded, NSError *error) {
if(succeeded) {
NSLog(#"Successfully retrieved %lu records from Parse.", (unsigned long)objects.count);
} else if (error) {
NSLog(#"Error");
}
}];
}
and then a UIButton is used to log the contents of the local datastore to the console:
-(IBAction)showDatastore {
// Query the Local Datastore
PFQuery *query = [PFQuery queryWithClassName:#"contacts"];
[query fromLocalDatastore];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
NSLog(#"Successfully retrieved %lu contacts from Datastore.", (unsigned long)objects.count);
} else {
// Log details of the failure
NSLog(#"Error: %# %#", error, [error userInfo]);
}
}];
}
In my sample the original array has 15 objects. Both the count's from each array initially are 15. I then remove an object from the server array and the initial PFQuery count is 14, but the local datastore count remains 15.
Parse's documentation states:
When an object is pinned, every time you update it by fetching or saving new data, the copy in the local datastore will be updated automatically.
But that doesn't seem to be the case... at least not with this recommended code. Is there something i'm missing?
It depends on how you are deleting the object. If you're using deleteEventually, then the deletion will propagate to the LDS
You can query from the local datastore using exactly the same kinds of queries you use over the network. The results will include every object that matches the query that's been pinned to your device. The query even takes into account any changes you've made to the object that haven't yet been saved to the cloud. For example, if you call deleteEventually, on an object, it will no longer be returned from these queries.
But any other method requires explicit unpinning if you want it to work.
deleteEventually is the prefered method I believe.
I have a view controller with inside table and I want to fill her with an array saved on Parse. To download the data I use this code:
PFQuery *query = [PFQuery queryWithClassName:#"myClass"];
[query whereKey:#"X" equalTo:#"Y"];
[query getFirstObjectInBackgroundWithBlock:^(PFObject *object, NSError *error) {
if(error==nil){
myArray=[object objectForKey:#"Z"];
NSLog(#"%#",myArray);
}
}];
}
Now I display it inside myarray the data on parse. But if I use arrays to populate the table it is always me empty. I used NSLog and I saw that outside of the method [query getFirstObjectInBackgroundWithBlock: ^ (PFObject * object, NSError * error) my array is always empty.
How can help me?
Fetching data from a remote database takes a little time. The parse functions that take block params run asynchronously. See the comments within your slightly modified code...
[query getFirstObjectInBackgroundWithBlock:^(PFObject *object, NSError *error) {
if(error==nil){
// this appears first in the file, but runs later
// after the request is finished
myArray=[object objectForKey:#"Z"];
NSLog(#"%#",myArray);
// tell our view that data is ready
[self.tableView reloadData];
}
}];
// this appears second in the file, but runs right away, right
// when the request is started
// while execution is here, the request isn't done yet
// we would expect myArray to be uninitialized
Be sure, in your datasource methods e.g. numberOfRows to answer myArray.count. And use the data in the array myArray[indexPath.row] when building the table view cell.
I am new with Parse and Ios-development.
I develop a ios-app that use Parse as backend.
I have got the main-function to work now, but i have a BIG problem.
I want to create a separate class for my API-handling to Parse. As i set it up now i have my parse-code directly in my view-controllers and as far as i know that not that nice coding.
But, the issue is to handle the background-jobs. Let say if i want to do a GET from the server, this can be done in a background-thread, just using "findObjectsInBackgroundWithBlock"
The problem is when i move this method to a separate API-class. Then my ViewController ask my API-class to get all the objects an the API-class will return it as soon its done. It will nor run in the background, i cant return a NSMutableArray with objects to the viewController until the fetch is done.
I have thinking that i maybe can get the data from parse synchronously in my API-class by using [query findObjects:&error] , as long as i figure out how to create my get-method in the API-class to run asynchronously.
I have try to create my API-method as a asynchronously method using blocks but will not run in background on a separate thread. (I am new to blocks an dont evan no if thats the correct way to crate a method that will run in a separate thread when using it)
Here is my API-method (Class: APIClient)
+ (void) GETAllShoppingGroups:(void (^) (NSMutableArray*))completionBlock{
//Create a mutable array (nil)
NSMutableArray *shoppingGroupsArray = nil;
//Create query for class ShoppingGroupe
PFQuery *query = [ShoppingGroupe query];
//Filter - find only the groups the current user is related to
[query whereKey:#"members" equalTo:[PFUser currentUser]];
//Sort Decending
[query orderByDescending:#"createdAt"];
//Tell Parse to also send the real member-objects and not only id
[query includeKey:#"members"];
//Send request of query to Parse with a "error-pointer"and fetch in a temp-array
NSError *error = nil;
NSArray *tempArray = [NSArray arrayWithArray:[query findObjects:&error]];
//Check for success
if (!tempArray) {
NSLog(#"%#", error);
NSLog(#"ERROR: %#", [error userInfo][#"error"]);
return completionBlock(shoppingGroupsArray);
} else {
//Seccess
shoppingGroupsArray = tempArray.mutableCopy;
completionBlock(shoppingGroupsArray);
}
}
Here is my ViewController Class (Class: ShoppingGruopViewController)
- (void) getAllObjects{
//Init array if nil
if (!self.shoppingGroupeArray) {
self.shoppingGroupeArray = [[NSMutableArray alloc]init];
}
//Remove old objects
[self.shoppingGroupeArray removeAllObjects];
//Get objects
[APIClient GETAllShoppingGroups:^(NSMutableArray* completionBlock){
if (completionBlock) {
[self.shoppingGroupeArray addObjectsFromArray:completionBlock]; }
[self.tableView reloadData];
}];
}
When I upload any data onto the Parse cloud, it stores the row at the top of the table. So every time a new row is added it gets stored at the top.
However, when I retrieve all the rows, the data is retrieved bottom up approach.
So let's say initially cloud is empty.
Push a
Cloud Looks like : a
Push b
Cloud Looks like : b a
Push c
Cloud Looks like : c b a
And now when I retrieve the data, i get it like: a b c
Now what I want is when data is inserted it is put at the 2nd location and not the first location.
Example:
Initial Cloud : "X"
Push a: "X" a
Push b: "X" b a
Push c: "X" c b a
Is there any way I can push data in Parse like this?
I'm doing because when I retrieve data, I wish to execute a method after all the data is retrieved in the background thread. So this way when I reach at X, I can call my method.
Found a solution to the problem....different approach though:
I count the number of objects for that query
Keep a counter increasing every time a record is fetched
when counter reached = total number of objects , then execute method.
NSInteger count= [query countObjects];
for (PFObject *obj in objects) {
[Names addObject:LastName];
if ([Names count] == count) {
[self getResults];
} }
^^^ solution is wrong
This way apparently does block the main thread, so there's a possibility of the app being killed.
Does anyone have any other solutions?
The data stored in the Parse Cloud is in an arbitrary order. Due to the way they currently store the data you may see new data at the end but this behaviour should not be relied upon.
If you want to retrieve data in a specific order then you should add a sorting operation to your PFQuery rather than trying to store the data in a specific order.
I am not sure what you are trying to achieve with your second code block. What does [self getResults] do?
If you want to execute some code after the results have been retrieved, why not just use findObjectsInBackgroundWithBlock?
This allows you to specify code to be executed once the data is received -
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
// The find succeeded.
names = [NSMutableArray arrayWithArray:objects];
// Do something with the found objects
for (PFObject *object in objects) {
NSLog(#"Object Name: %#", object.objectId);
}
} else {
// Log details of the failure
NSLog(#"Error: %# %#", error, [error userInfo]);
}
}];
If you need to update any UI from the block (or a method called within the block) then you should perform that on the main thread -
dispatch_async(dispatch_get_main_queue(), ^{
[self updateMyUIWithResult:objects];
});
So, putting it together you get -
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
dispatch_async(dispatch_get_main_queue(), ^{
[self updateMyUIWithResult:objects];
});
} else {
// Log details of the failure
NSLog(#"Error: %# %#", error, [error userInfo]);
}
}];