In have a problem to Change CalUsed value I want to put Data in NSNumber *CalUsed1 to CalUsed for use in other function
#property (nonatomic, assign) NSNumber *CalUsed;
- (void) GetCalUsed{
PFUser *user = [PFUser currentUser];
[PFCloud callFunctionInBackground:#"CalUsed"
withParameters:#{#"user": user.objectId}
block:^(NSNumber *CalUsed1, NSError *error) {
if (!error) {
NSLog(#"Calories : %#",CalUsed1);
CalUsed = CalUsed1;
}
CalUsed = CalUsed1;
NSLog(#"TDEE IN FN is : %#",CalUsed);
}];
}
- (void) TestPrint{
NSLog(#"TDEE OUT FN : %#",CalUsed);
}
RESULT
TDEE OUT FN : (null)
Calories : 700
TDEE IN FN : 700
but I need global variable "CalUsed" change value to 700
Some Parse methods have a block in them. Because there will always be a delay in retrieving the data, there needs to be someone that the app "waits" until the data is retrieved. Unfortunately there is no easy way to do this so the block is there so anything that is within it will run AFTER the data is retrieved. If you put your NSLog in the block, it should work.
You have to remember that goal of any program is to execute the code as fast as possible. So when you call the Parse method, anything below it, NOT in the block will continue to run.
Update
If your going to use Parse you must understand the following (if you don't, there's no use in using Parse). Any parse functions that have a block will act differently than you would expect.
- (void) GetCalUsed{
PFUser *user = [PFUser currentUser];
[PFCloud callFunctionInBackground:#"CalUsed" //This is the Parse function
withParameters:#{#"user": user.objectId}
block:^(NSNumber *CalUsed1, NSError *error) { // This is where the block starts
if (!error) { //if the block retrieves the data with no problem, this will run
NSLog(#"Calories : %#",CalUsed1);
CalUsed = CalUsed1;
}
CalUsed = CalUsed1;
NSLog(#"TDEE IN FN is : %#",CalUsed);
}];
}
The thing about callFunctionInBackground is that there isn't really a way to return a value. So try to follow me: When the function is called, it will create a request to the Parse database to retrieve the data. But no matter how strong your connection, there will always be a delay (even if it is by milliseconds it still counts as a delay). Like I said before, the compiler wants to execute the code as fast as possible so this delay is going to cause problems. Using the above code, the Parse function will be called, however, as the app waits to retrieve the data, it's going to continue executing everything OUTSIDE of the Parse method. This is why your getting the null value (the NSLog outside of callFunctionInBackground is executed before the data is returned from Parse). To prevent this from happening, anything that involves calUsed1 or calUsed must go inside the block. Just like this:
- (void) GetCalUsed{
PFUser *user = [PFUser currentUser];
[PFCloud callFunctionInBackground:#"CalUsed"
withParameters:#{#"user": user.objectId}
block:^(NSNumber *CalUsed1, NSError *error) {
if (!error) {
NSLog(#"Calories : %#",CalUsed1);
CalUsed = CalUsed1;
//HERE YOU CAN MANIPULATE THE DATA HOWEVER YOU WISH. YOU CAN CALL A METHOD OR DO SOMETHING ELSE (aka, you can do whatever you want here)
}
}];
}
All in all, everything inside the block will be run only after the data from Parse has been retrieved...no matter how long it takes. If you still have questions feel free to ask.
Related
I have an application that saves public urls and then uploads them syns a reference to them in parse when the a button is clicked. After I make the calls to parse, in the block, I want to reset the array that I'm using, but I'm a little unclear if removing the reference will create some sort of null pointer error. Here is my code
for (NSString *theString in sharedDataController.filesToUpload) {
// Create PFObject with recipe information
PFObject *parseImage = [PFObject objectWithClassName:#"Photos"];
[parseImage setObject:theString forKey:#"myURL"];
[parseImage setObject:object forKey:#"photoUser"];
[parseImage saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (!error) {
// Show success message
NSLog(#"Parse Image Saved");
} else {
NSLog(#"Parse Image Error");
}
}];
// if I delete the contents sharedDAtaController.filesToUpload here, will that create an issue
}
You are creating objects from array and uploading all objects one by one. But Parse also provide you a bulk upload in array. So create array of PFObjects and upload it to Parse
NSMutableArray *arrParseObjects = [[NSMutableArray alloc]init];
for (NSString *theString in sharedDataController.filesToUpload) {
// Create PFObject with recipe information
PFObject *parseImage = [PFObject objectWithClassName:#"Photos"];
[parseImage setObject:theString forKey:#"myURL"];
[parseImage setObject:object forKey:#"photoUser"];
[arrParseObjects addObject:parseImage];
// if I delete the contents sharedDAtaController.filesToUpload here, will that create an issue
}
[PFObject saveAllInBackground:arrParseObjects block:^(BOOL succeeded, NSError *PF_NULLABLE_S error){
if (succeeded) {
// You just saved all objects in Parse
}else{
}
}];
End if we talk about your crash on removing object. We never can remove any object from the array while we are looping through it. 1 solution to it is that we need to use reverse enumeration to it.
[sharedDAtaController.filesToUpload enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
// Remove the object after work
// Make sure your array is a NSMutableArray.
}];
Yes it will create a problem. The calls to saveInBackgroundWithBlock are not finished outside of the completion block. You will need some method to coalesce all the calls and only delete sharedDAtaController.filesToUpload once the last completion block fires.
You could make an atomic property "pendingSaves", increment it each time before you call saveInBackgroundWithBlock, decrement it in the completion block, and delete sharedDAtaController.filesToUpload in the completion block of saveInBackgroundWithBlock only if pendingSaves == 0
I made the following test class to try out retrieving data from Parse:
-(void)retrieveDataFromParse
{
PFQuery *query = [PFQuery queryWithClassName:#"TestObject"];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if(!error){
for (PFObject *object in objects){
NSString *nameFromObject = [NSString stringWithFormat:#"%#", [object objectForKey:#"Name"]];
NSString *dateFromObject = [NSString stringWithFormat:#"%#", [object createdAt]];
NSString *scoreFromObject = [NSString stringWithFormat:#"%#", [object objectForKey:#"Score"]];
[self addNewScore:scoreFromObject andDate:dateFromObject forUserName:nameFromObject];
NSLog(#"The dictionary is %#", self.scoreDictionary); //<-- here it works printing out the whole dictionary
}
} else {
NSLog(#"Error: %# %#", error, [error userInfo]);
}
}];
NSLog(#"The dictionary is %#", self.scoreDictionary); //<- but after the block is called, here the dictionary is again empty...
}
Per the commented section inside the code, when I print self.scoreDictionary inside the code, it works out fine, and I see my entire dictionary as it incrementally gets filled. However, after the block ends, when I print the dictionary again, it is now empty. I double checked with the query API docs, but I still am unsure what I am doing incorrectly.
The last NSLog(#"The dictionary is %#", self.scoreDictionary) statement does not actually execute after the block completes. It executes after the findObjectsInBackgroundWithBlock method returns. findObjectsInBackgroundWithBlock presumably runs something in a separate thread, and your block may not actually execute at all until some length of time after that last NSLog statement. Graphically, something like this is probably happening:
Thread 1
--------
retriveDataFromParse called
invoke findObjectsInBackgroundWithBlock
findObjectsInBackgroundWithBlock queues up work on another thread
findObjectsInBackgroundWithBlock returns immediately |
NSLog statement - self.scoreDictionary not yet updated |
retriveDataFromParse returns |
. V
. Thread 2, starting X milliseconds later
. --------
. findObjectsInBackgroundWithBlock does some work
. your block is called
. for-loop in your block
. Now self.scoreDictionary has some data
. NSLog statement inside your block
You probably want to think about, what do you want to do with your scoreDictionary data after you have retrieved it? For example, do you want to update the UI, call some other method, etc.? You will want to do this inside your block, at which point you know the data has been successfully retrieved. For example, if you had a table view you wanted to reload, you could do this:
for (PFObject *object in objects){
....
}
dispatch_async(dispatch_get_main_queue(), ^{
[self updateMyUserInterfaceOrSomething];
});
Note the dispatch_async - if the work you need to do after updating your data involves changing the UI, you'll want that to run on the main thread.
The last NSLog(#"The dictionary is %#", self.scoreDictionary) is executed before the completion block executes. By the time, self.scoreDictionary will be empty for sure.
Besides, the completion block will be executed on main thread. You can refer to the following link.
https://parse.com/questions/what-thread-does-findobjectsinbackgroundwithblock-complete-on
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]);
}
}];
I want to save PFObjects from a Parse query in an NSMutableArray that my class has called listdata. I will later use the listdata array. When I traced through my code, it updated the highScoreObjects array for each object found. But when I try to set the listdata array to the highScoreObjects array, the highScoreObjects array is empty. Is there a way to keep the data after the query ends?
NSMutableArray *highScoreObjects = [[NSMutableArray alloc] initWithCapacity:5];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
// The find succeeded.
NSLog(#"Successfully retrieved %d scores.", objects.count);
// Do something with the found objects
for (PFObject *object in objects) {
[highScoreObjects addObject:object];
NSLog(#"%#", object.objectId);
}
dispatch_async(dispatch_get_main_queue(), ^ {
[self.tableView reloadData];
});
} else {
// Log details of the failure
NSLog(#"Error: %# %#", error, [error userInfo]);
}
}];
self.listData = highScoreObjects;
I also tried keeping the line self.listData = highScoreObjects;
inside the self.listData = highScoreObjects; loop. This didn't make any difference.
It isn't that it isn't set. It's that it isn't set yet. This is because you're using findObjectsInBackgroundWithBlock and the asynchronous process hasn't completed yet.
Move your assignment (self.listData = highScoreObjects;) into the block, just before you dispatch the request to reload the table view.
This is yet another case of not understanding the nature of asynchronous programming.
Consider this situation:
You want to make an egg sandwich. You put the eggs on to boil, and set an alarm for when they're cooked to get them out, peel them, cut them up and add them to your sandwich. While you wait you get the bread and butter it, then wait for the alarm to go off.
Your call to findObjectsInBackgroundWithBlock is putting the eggs on to boil. The block you pass it is the alarm and what you plan to do with the eggs once cooked.
Your code above is akin to putting the eggs on to boil, then straight away trying to use the uncooked/partially-cooked eggs on your sandwich. Makes a big mess.
The solution is to call a method at the end of the block your pass to the method.
I'm basically implementing a fancier NSURLConnection class that downloads data from a server parses it into a dictionary, and returns an NSDictionary of the data. I'm trying add a completion block option (in addition to a delegate option), but it crashes anytime I try to store that data in another class.
[dataFetcher_ fetchDataWithURL:testURL completionHandler:^(NSDictionary *data, NSInteger error) {
contentDictionary_ = data;
}];
I can NSLog that data just fine, and basically do whatever I want with it, but as soon as I try to save it into another variable it crashes with a really obscure message.
EDIT: the crash message is EXC_BAD_ACCESS, but the stack trace is 0x00000000 error: address doesn't contain a section that points to a section in a object file.
I'm calling this function in the init method of a singleton. It DOES let me save the data if I set this in the completion block.
[SingletonClass sharedInstance].contentDictionary = data
But then the app gets stuck forever because sharedInstance hasn't returned yet, so the singleton object is still nil, so sharedInstance in the completion block calls init again, over and over.
EDIT 2: The singleton code looks like this:
+ (SingletonClass*)sharedInstance {
static SingletonClass *instance;
if (!instance) {
instance = [[SingletonClass alloc] init];
}
return instance;
}
- (id)init {
self = [super init];
if (self) {
dataFetcher_ = [[DataFetcher alloc] init];
NSString *testURL = #"..."
[dataFetcher_ fetchDataWithURL:testURL completionHandler:^(NSDictionary *data, NSInteger error) {
[SingletonClass sharedInstance].contentDictionary = data;
}];
}
return self;
}
Like I said, this works fine but repeats the initialize code over and over until the app crashes. This only happens the first time I run the app on a device, because I cache the data returned and it doesn't crash once I have the data cached. I would like to be able to just say self.contentDictionary = data, but that crashes.
Specify a variable to be used in the block with the __block directive outside of the block:
__block NSDictionary *contentDictionary_;
[dataFetcher_ fetchDataWithURL:testURL completionHandler:^(NSDictionary *data, NSInteger error) {
contentDictionary_ = data;
}];
You're invoking recursion before ever setting the "instance". (which I now see you understand from OP).
In your block, you can use the ivar or an accessor instead of
[SingletonClass sharedInstance].contentDictionary
use:
_contentDictionary = [data copy]; or self.contentDictionary=data;
assuming that the ivar backing the contentDictionary property is _contentDictionary.
It sounds like you tried self.contentDictionary and it failed? I got it to work in a test, with ARC turned, so there may be something about your dataFetcher that is affecting this. In my test dataFetcher just returns a dictionary with a single element.
Turns out the issue was with a bunch of different parts. My URL was empty sometimes, and my data fetcher would just fail immediately and call the completion block. In my completion block I hadn't included any error handling, so if the singleton class hadn't initialized, it would repeat forever. With a real URL this doesn't happen.
I still would like to figure out why it crashes when I try to assign the data to an ivar, though.