how can I ignore new fetching if one is already running. Here is one example of my code:
So if i call [self getParticipants] how to make sure to ignore if already running. The only solution I've got is to create BOOL property "inMiddleOfFetching" but I dont want to create anther BOOL property just for this. Is there a better solution?
- (void)getParticipants {
PFQuery *participantsQuery = [self.participantsRelation query];
[participantsQuery includeKey:#"client"];
[participantsQuery findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (objects)
self.participants = objects;
}];
}
This is not simpler but it is better, you can use bolts to create a single task for the query, this way if it is called multiple times it will only run once but all calls will return the value at the same time.
Something like this:
#property BFTask* task;
- (BFTask *)getParticipants {
if (!_task) {
PFQuery *participantsQuery = [self.participantsRelation query];
[participantsQuery includeKey:#"client"];
_task = [participantsQuery findObjectsInBackground];
}
return _task;
}
Then to get the result:
[[self getParticipants] continueWithBlock:^id(BFTask *task) {
if(!task.error){
self.participants = task.result;
}
_task = nil; //if you want to run the query again in the future
return nil;
}];
Related
I'm trying to develop and Action Extension for iOS9.1 that is supposed to query Parse for some data.
I've added, enabled and tested Parse in the extension and I'm successful at creating test objects and checking for current user.
I can not get the code inside Parse's query method
[query findObjectsInBackgroundWithBlock:^ to execute. LLDB just keeps skipping it so I'm really at a loss.
This code executes perfectly within the container app so I'm a bit confused.
- (void)viewDidLoad
{
[super viewDidLoad];
[Parse enableLocalDatastore];
[Parse enableDataSharingWithApplicationGroupIdentifier:#"group.com.app.slinky"
containingApplication:#"com.app.Slinky"];
[Parse setApplicationId:#"xxxxx"
clientKey:#"xxxxx"];
for (NSExtensionItem *item in self.extensionContext.inputItems) {
for (NSItemProvider *itemProvider in item.attachments) {
if ([itemProvider hasItemConformingToTypeIdentifier:(NSString *)kUTTypePropertyList]) {
[itemProvider loadItemForTypeIdentifier:(NSString *)kUTTypePropertyList options:nil completionHandler:^(NSDictionary *jsDict, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
NSDictionary *jsPreprocessingResults = jsDict[NSExtensionJavaScriptPreprocessingResultsKey];
NSString *pageTitle = jsPreprocessingResults[#"title"];
NSString *pageURL = jsPreprocessingResults[#"URL"];
if ([pageURL length] > 0) {
self.siteURL.text = pageURL;
self.URLstring = pageURL;
}
if ([pageTitle length] > 0) {
self.siteTitle.text = pageTitle;
}
});
}];
break;
}
}
}
[self queryParse];
}
-(void)queryParse{
PFQuery *query = [self.friendsRelation query];
[query orderByAscending:#"username"];
**[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (error) {
NSLog(#"%# %#", error, [error userInfo]);
} else {
self.friends = objects;
[self.tableView reloadData];
}
}];**
}
There are some limit to fetch Objects from Parse. Read Here
One has a limit of obtaining 100 objects, but anything from 1 to 1000 has a valid limit.
You have to set the limit again for the query for next objects and skip the properties of PFQuery.
You will have to query again and again until you reach the total count.
For Example - If you have 3000 objects to be fetched, then you have to query 3 times with a count of say 1000.
OR
Call [self queryParse]; inside dispatch_async(dispatch_get_main_queue(), ^{ }); block.
Hope this might workout.
This was just a result of a 12 hour refractoring marathon :-(
When I moved the queryParse call into its own method. I lost the definition of
self.friendsRelation = [[PFUser currentUser] objectForKey:#"friendsRelation"];
essentially sending a bad query... I don't know why it isn't return an error but regardless I can check that off now.
Thank you all for you help!
I need to upload some data to server. Just before doing so, I fetch an entity that this data should be bounded to(simple relationship). If there is no such entity, I want to create it then and continue uploading process. The problem, though, is that if there are too many requests, all requests will end up faster than creation of the entity, thus many entities will be created. The solution is to dispatch the block of code synchronously, but on a different thread. How do I do this?
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH,0), ^{
PFQuery *query = [PFQuery queryWithClassName:#"FileOwner"];
[query whereKey:#"identifier" equalTo:#"ABCDEFG"];
PFObject *fileOwner = [query getFirstObject];
if(fileOwner){
[self continueUploadingData:data completionHandler:completionBlock];
}
else{
PFObject *newFileOwner = [PFObject objectWithClassName:#"FileOwner"];
newPhotoOwner[#"identifier"] = #"ABCDEFG";
[newPhotoOwner saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if(succeeded)[self continueUploadingData:data completionHandler:completionBlock];
}];
}
});
This works, but obviously blocks the main thread. I can't use detachSelector: toTarget: withObject: because I have more than one argument.
The general purpose advice that will help here is to build the atomic functions with methods that take completion blocks (like parse's, maybe like your continueUploading method).
In this case, we need a function that will look for an object, return it if it's found, or create and return it if it's not found, all asynch...
- (void)findOrCreateFileOwnerWithIdentifier:(NSString *)identifier completion:(void (^)(PFObject *, NSError *))completion {
PFQuery *query = [PFQuery queryWithClassName:#"FileOwner"];
[query whereKey:#"identifier" equalTo:identifier];
[query getFirstObjectInBackgroundWithBlock:^(PFObject *object, NSError *error) {
if (object) {
return completion(object, nil);
} else {
PFObject *newFileOwner = [PFObject objectWithClassName:#"FileOwner"];
newFileOwner[#"identifier"] = identifier;
[newFileOwner saveInBackgroundWithBlock:completion];
}
}];
}
Now you can say...
[self findOrCreateFileOwnerWithIdentifier:#"ABCDEFG" completion:^(PFObject *fileOwner, NSError *error) {
if (!error) {
[self continueUploadingData:data completionHandler:completionBlock];
} else {
// handle error... probably pass it to completionBlock
// not shown because the OP code doesn't show how the block is declared
}
}];
I am using this query to find users, it works but it just shows me the first user. I want it to show me the user with the text of an UITextField.
How can I do that ?
(I have a textfield and there I type in a name and then it should show the parsed users with the name)
PFQuery *query = [PFUser query];
NSArray *users = [query findObjects];
userQuerys.text = users[0][#"username"];
Thanks very much
This code will fetch you all the PFUsers in which username is equal to the name parameter:
- (void)searchUsersNamed:(NSString *)name withCompletion:(void (^)(NSArray *users))completionBlock {
PFQuery *query = [PFUser query];
[query whereKey:#"username" equalTo:name];
[query findObjectsInBackgroundWithBlock:^(NSArray *users, NSError *error) {
if (!error) {
// we found users with that username
// run the completion block with the users.
// making sure the completion block exists
if (completionBlock) {
completionBlock(users);
}
} else {
// log details of the failure
NSLog(#"Error: %# %#", error, [error description]);
}
}];
}
An example, if you need to update the UI with the result, for example, a table:
- (void)someMethod {
// we will grab a weak reference of self to perform
// work inside the completion block
__weak ThisViewController *weakSelf = self;
//replace ThisViewController with the correct self class
[self searchUsersNamed:#"Phillipp" withCompletion:^(NSArray *users) {
//perform non-UI related logic here.
//set the found users inside the array used by the
//tableView datasource. again, just an example.
weakSelf.users = users;
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
//pefrorm any UI updates only
//for example, update a table
[weakSelf.tableView reloadData];
}];
}];
}
A small note: the completionBlock here wont run if there is an error, but it will run even if no users were found, so you gotta treat that (if needed. in this example, it was not needed).
Avoid running non-UI related logic on that mainQueue method, you might lock the Main thread, and that`s bad user experience.
I am having trouble saving a PFRelation I have this code:
//set up the query
PFQuery *query = [PFQuery queryWithClassName:#"messageBank"];
[query whereKey:#"username" equalTo:name];
__weak User *weakSelf = self;
[query getFirstObjectInBackgroundWithBlock:^(PFObject *object, NSError *error) {
if(error) {
NSLog(#"No such user");
handler(NO, error,NO,NO);
}
else{
[weakSelf.friendsRelation addObject:object];
[weakSelf.friends addObject:object];
//save in the background
[weakSelf.messageBank saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if(error) {
NSLog(#"Save error");
}
else {
NSLog(#"no error");
}
}];
handler(YES,nil,NO,NO); //no errors
//so the friend is added to the friends array, all we need to do is reload the table data don't need to init the array again, the relation is also added to the relation item so don't need to init that again
}
}];//end block
My code finds the messageBank object fine but it won't save it to the PFRelation friends. It doesn't even attempt to call [weakSelf.messageBank saveInBackgroundWithBlock.... weakSelf.messageBank is the local PFObject and weakSelf.friends is it's PFRelation. Anyone have any ideas what could be going wrong here? If I have a PFRelation in class A is it okay to have pointers in that relation to other objects in class A? Does it need to be in a different class? Any help would be much appreciated!!!
Here's a cleaned up version of the code that fetches an object and adds to its relation, and saves it...
PFQuery *query = [PFQuery queryWithClassName:#"messageBank"]; // by convention class names should be capital MessageBank, but using yours
[query whereKey:#"username" equalTo:name]; // better form is self.name assuming it is an attribute of self
[query getFirstObjectInBackgroundWithBlock:^(PFObject *object, NSError *error) {
if (!error) {
// see note below about weakSelf
// assume self is a PFObject subclass with two relations
// (and generated setters) called friendsRelation and friends
[self.friendsRelation addObject:object];
[self.friends addObject:object];
// notice we save self here. that's who changed in the two preceding lines
[self saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if(!error) {
// success
} else {
// handle error
}
}];
} else {
// handle error
}
}];
Please notice that there's no need to declare a __weak copy of the self pointer (though, there's no harm in it). That idiom is used to avoid a retain cycle between self and the block's owner. You need it only when self is the blocks owner (directly or indirectly). This isn't the case with parse's completion blocks.
I have created a new method which returns a BOOL, shown below.
+(BOOL)checkIfGameAlreadyExistsAgainst:(PFUser *)opponentUser {
// Find all the games where the current user is user1 and the opponentUser is user2
PFQuery *currentUserIsUser1 = [PFQuery queryWithClassName:#"Game"];
[currentUserIsUser1 whereKey:kMESGameUser1 equalTo:[PFUser currentUser]];
[currentUserIsUser1 whereKey:kMESGameUser2 equalTo:opponentUser];
[currentUserIsUser1 whereKey:kMESGameIsActive equalTo:[NSNumber numberWithBool:YES]];
[currentUserIsUser1 findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (objects) {
// We have games where the current user is user1
// NEED TO RETURN NO TO THIS METHOD AND NOT RUN FURTHER IN METHOD...
NSLog(#"Results returned for existing game where current user is User1. Results: %#",objects);
} else {
// If there are no objects from first query and no error we run the second query
if (!error) {
// Find all the games where the current user is user2 and the opponentUser is user1
PFQuery *currentUserIsUser2 = [PFQuery queryWithClassName:#"Game"];
[currentUserIsUser2 whereKey:kMESGameUser1 equalTo:opponentUser];
[currentUserIsUser2 whereKey:kMESGameUser2 equalTo:[PFUser currentUser]];
[currentUserIsUser2 whereKey:kMESGameIsActive equalTo:[NSNumber numberWithBool:YES]];
[currentUserIsUser2 findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (objects) {
// We have games where the current user is user2
// NEED TO RETURN NO TO THIS METHOD AND NOT RUN FURTHER IN METHOD...
NSLog(#"Results returned for existing game where current user is User2. Results: %#",objects);
}
}];
}
}
}];
return NO;
}
The problem I have is how to return a YES value within a block within the method.
See the sections in the method which say // NEED TO RETURN NO TO THIS METHOD AND NOT RUN FURTHER IN METHOD...
How can I return YES here. If I add return YES I get an incompatible pointer types error.
Further to this, once I have the method returning YES, how do I call this method and do something depending on the result.
For example I need to call this method and if it is true then do something else, if not do nothing...
I am not sure what you are asking, so here is a guess: you wish your block to return a value to checkIfGameAlreadyExistsAgainst.
When a block in constructed it usually makes a copy of any value referenced from its environment. If you wish your block to modify a variable in its environment you must mark that variable with __block. In your code this would look something like:
__block BOOL blockStatus = YES;
[currentUserIsUser1 findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error)
{
...
blockStatus = NO;
}
];
if (!blockStatus)
{
...
}
Important: The name of the method you are calling, findObjectsInBackgroundWithBlock, suggests that the block may not be called synchronously, which means the call may return before the block is executed. If this is the case you need to tackle the issue in a different way; which may involve calling a synchronous equivalent of findObjectsInBackgroundWithBlock or modifying checkIfGameAlreadyExistsAgainst so that it accepts a block which it calls asynchronously with its result rather than returning a value directly.
HTH