I am trying to return a bool value from a class method I call that has a block in it. I get the error, Incompatible block pointer types sending.... How would I get around this? I just want to know if the class method I call completes with or without error...
+ (BOOL)saveSelectedDepartmentsToParse:(NSMutableDictionary *)dictionary {
NSArray *array = [dictionary allKeysForObject:#"YES"];
NSMutableArray *trimmedArray = [[NSMutableArray alloc] init];
for (NSString *string in array) {
NSString *final = [string removeAllInvalidCharacters];
[trimmedArray addObject:final];
}
NSLog(#"Array = %#", trimmedArray);
PFInstallation *currentInstallation = [PFInstallation currentInstallation];
[currentInstallation removeObjectForKey:#"channels"];
[currentInstallation addObjectsFromArray:trimmedArray forKey:#"channels"];
[currentInstallation saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (error == nil) {
NSLog(#"Parse Save Succeeded");
[self saveDepartmentsDictionary:dictionary];
}
else {
NSLog(#"Parse Save Failed, %#", error.localizedDescription);
}
}];
}
I just want to know if the class method I call
This is a misunderstanding of how asynchronous code works. When you are supplying a block to saveInBackgroundWithBlock:, that code is not executed straight away. It's executed at some later point by the Parse framework, and whichever part of Parse that does so would get the return value if the block were defined to have one, which it isn't. Your block isn't executed at the point at which you write it, so you can't return anything at the point at which you write it.
Your code isn't calling the block, and you can't return values to your code from it. It doesn't make sense to do so. If another part of your code wants to know when the saving has finished, you'll need to use some other mechanism than return values, such as calling a method from your block, posting a notification, or Key-Value Observing.
From the block keyword InBackground:
[currentInstallation saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (error == nil) {
NSLog(#"Parse Save Succeeded");
[self saveDepartmentsDictionary:dictionary];
} else {
NSLog(#"Parse Save Failed, %#", error.localizedDescription);
}
}];
I guess the block is called asynchronously.
If you want to get the result, you can wait here until the block is executed, but this make the saveInBackgroundWithBlock useless.
So NSNotification may be better:
[currentInstallation saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (error == nil) {
NSLog(#"Parse Save Succeeded");
[self saveDepartmentsDictionary:dictionary];
[[NSNotificationCenter defaultCenter] postNotificationName:kNotificationParseSaveSucceeded object:nil];
} else {
NSLog(#"Parse Save Failed, %#", error.localizedDescription);
[[NSNotificationCenter defaultCenter] postNotificationName:kNotificationParseSaveFailed object: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!
Newbie question... I am trying to set a variable self.projectName in a code block but when I call it outside the code block, the value is not retained. After reading more about code blocks, it seems like there are some rules on when values become available but I'm still not clear why I can't set the value for use later...any help would be much appreciated!
PFQuery *query = [PFQuery queryWithClassName:#"ScheduledProjects"];
[query findObjectsInBackgroundWithBlock:^(NSArray *projects, NSError *error) {
if (!error) {
PFObject *project = [projects objectAtIndex:indexPath.row];
self.projectName = project[#"name"];
} else {
// Log details of the failure
NSLog(#"Error: %# %#", error, [error userInfo]);
}
}];
NSLog (#"project name = %#",self.projectName);
The block is meant to be called asynchronously, which means that after you defined it you don't know exactly when it will finish executing.
To use the variable try creating a callback function and calling it at the end of the block. There you will know for sure that it has been executed.
Example:
-(void)yourMethod{
PFQuery *query = [PFQuery queryWithClassName:#"ScheduledProjects"];
[query findObjectsInBackgroundWithBlock:^(NSArray *projects, NSError *error) {
if (!error) {
PFObject *project = [projects objectAtIndex:indexPath.row];
self.projectName = project[#"name"];
[self callback];//You call the method when your block is finished
} else {
// Log details of the failure
NSLog(#"Error: %# %#", error, [error userInfo]);
//Here you could call a different callback for error handling (or passing a success param)
}
}];
}
-(void) callback{
//Here you know the code has been executed
NSLog (#"project name = %#",self.projectName);
}
I'm getting stuck with an error when using my Watchkit Application. When I launch it, I ask the containing iOS app to get some data from network. The problem is that I get an error saying the containing app never calls 'reply()' :o But looking at my code, it should call it.
I tried to debug every step from openParentApplication to the 'reply()' call, and it seems to work well =X
Here is my code in the Watchkit extension
- (void)initDiaporamasWithSuccess:(void (^)())success andFailure:(void (^)(NSError*))failure {
NSLog(#"Ask to load diapos");
__weak typeof(self) weakSelf = self;
[WKInterfaceController openParentApplication:#{#"watchKit": #"watchKit.initDiapos"} reply:^(NSDictionary *replyInfo, NSError *error) {
if (error) {
NSLog(#"%#", error);
if (failure) {
failure(error);
}
return;
}
NSLog(#"got items : %#", replyInfo[#"diapos"]);
weakSelf.diaporamas = replyInfo[#"diapos"];
[weakSelf setDiaporama:replyInfo[#"firstDiapo"] AtIndex:0];
if (success) {
success();
}
}];
}
The result should be an NSDictionary containing an NSArray with some diaporamas basic informations, and an object (Diapo) containing the full informations of the first diaporama (e.g. self.diaporamas[0])
And here is the code in the containing app's AppDelegate :
- (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void (^)(NSDictionary *))reply {
// Maybe we could handle multiple watchKit extension calls that way ?
// Something like a key-value 'protocol' to run the right block of code
NSString *watchKitCall = userInfo[#"watchKit"];
NSLog(#"watchKit handled");
if ([watchKitCall isEqualToString:#"watchKit.initDiapos"]) {
[AppDelegate watchInitialObjects:^(NSDictionary *info) {
NSLog(#"Managed to get initial infos");
reply(info);
} failure:^(NSError *error) {
NSLog(#"Fail : %#", error);
reply(#{#"error": error});
}];
}
}
+ (void) watchInitialObjects:(void (^)(NSDictionary *info))success failure:(void (^)(NSError *error))failure {
NSDictionary *parameters = #{#"site" : #(14), #"limit" : #(10)};
[AppDelegate requestDiapoListWithParams:parameters success:^(NSArray *items) {
if ([items count] == 0)
{
NSError *error = [NSError errorWithDomain:#"com.domain.app" code:404 userInfo:nil];
failure(error);
return;
}
Diapo *firstDiapo = [items firstObject];
[AppDelegate requestDiapoDetailWithDiapo:firstDiapo success:^(Diapo *diapo) {
if (!diapo)
{
NSError *error = [NSError errorWithDomain:#"com.domain.app" code:404 userInfo:nil];
failure(error);
return;
}
NSDictionary *result = #{
#"firstDiapo" : diapo,
#"diapos" : items
};
success(result);
} failure:^(NSError *error) {
failure(error);
}];
} failure:^(NSError *error) {
failure(error);
}];
}
In the watchKitHandler, I call watchInitialObjects to get the diaporamas array and the first diaporama's informations.
In the watchInitialObjects, I make a first network call to get the array, and on success, I make an other network call to get the firs diaporama informations.
To make the calls and map the JSON into objects, I use RESTKit
I really don't get what could be the error =x
UPDATE
I forgot to write the error I get, here it is :
Error Domain=com.apple.watchkit.errors Code=2 "The UIApplicationDelegate in the iPhone App never called reply() in -[UIApplicationDelegate application:handleWatchKitExtensionRequest:reply:]" UserInfo=0x7fcb53e12830 {NSLocalizedDescription=The UIApplicationDelegate in the iPhone App never called reply() in -[UIApplicationDelegate application:handleWatchKitExtensionRequest:reply:]}
And I kept trying to know why I get this error, and I think I found it:
It seems that there is a (very little) timeout to do the work in the containing app. But I mapped the JSON data I received directly in the containing app and then, send those custom objects in the reply(). But when I removed the mapping part, it worked well !
So...that's why I think that was the problem =X
Does anybody could approve my thoughts or corrects me ?
After hours of searching and testing different codes, I finally found my problem...and it's obvious when we read the Apple documentation about 'application:handleWatchKitExtensionRequest:reply:' seriously...
here is the answer : (it's in the documentation)
The contents of the dictionary must be serializable to a property list file.
Which means that objects can ONLY be dictionaries, arrays, strings, numbers (integer and float), dates, binary data, or Boolean values
...I feel dumb ><
Using a Background Fetch we trigger a method
- (void)sendPush:(SPUserInfo *)userInfo {
PFQuery *findUserQuery = [PFUser query];
[findUserQuery whereKey:#"objectId" equalTo:userInfo.userID];
[findUserQuery findObjectsInBackgroundWithBlock:^(NSArray* arr, NSError* err) {
if (err) {
MLog(#"Error Finding User to Spark: %#", err.description);
}
else {
if (arr.count) {
MLog(#"User found");
PFObject *spark = [PFObject objectWithClassName:kSparkClassName];
[...]
[spark saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (!error) {
//save
// send push through Parse
[...]
PFPush *push = [PFPush push];
NSString *channel = [NSString stringWithFormat:#"u_%#", receiver.objectId];
[push setChannels:#[channel]];
[push setData:data];
[push sendPushInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (nil == error) {
MLog(#"Push success");
//do the stuff
[self.queueSendingSpark removeObject:userInfo];
[self saveSendingSparkQueue];
}
else {
MLog(#"Push Error : %#", error.description);
}
}];
}
else {
MLog(#"Spark is not saved on parse | %#", error);
}}];
}
else {
MLog(#"ZERO Users");
}
}
}];
}
This is all fine and dandy if the app is running in the foreground or if the phone is awake. But when the iPhone is locked/asleep, the findObjectsInBackgroundWithBlock never returns any data.
Is this expected behaviour? Is there a way to ensure this query, and others, are returning as normal when a device is asleep?
So theres a property that you can use to handle this...
#property (nonatomic, assign) UIBackgroundTaskIdentifier fileUploadBackgroundTaskId;
Within your init method set the property like so:
self.fileUploadBackgroundTaskId = UIBackgroundTaskInvalid;
Set the value when needed
// Request a background execution task to allow us to finish uploading the photo even if the app is backgrounded
self.fileUploadBackgroundTaskId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[[UIApplication sharedApplication] endBackgroundTask:self.fileUploadBackgroundTaskId];
}];
Run your query, then when the query is finished...
[[UIApplication sharedApplication] endBackgroundTask:self.fileUploadBackgroundTaskId];
I have an array of string's that I am trying to save to parse as the channels for push notifications. The array is correct, put I have no idea what is going on. Can anybody throw some light on this? Thanks.
Error:
Error: Bad channel name: TestString123 (Code: 112, Version: 1.1.30)
Code:
- (void)saveSelectedDepartmentsToParse:(NSMutableDictionary *)dictionary {
NSArray *array = [dictionary allKeysForObject:#"YES"];
NSLog(#"Array = %#", array);
PFInstallation *currentInstallation = [PFInstallation currentInstallation];
[currentInstallation addObjectsFromArray:array forKey:#"channels"];
[currentInstallation saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (error == nil) {
NSLog(#"Parse Save Succeeded");
}
else {
NSLog(#"Parse Save Failed, %#", error.localizedDescription);
}
}];
}
The reason is because Parse does not accept whitespaces or any special characters in the channels. After removing all white spaces and special characters, the operation succeeded.