Hi I need to download over 2000 records from azure , the maximum you can download is 1000 at the time , so I need to use a completion handler to download 200 at the time.
They posted this code as an example but I don't know how to use.
If I copy this to Xcode there is an error
(bool)loadResults() - Error " Expect Method Body "
Returning data in pages
Mobile Services limits the amount of records that are returned in a single response. To control the number of records displayed to your users you must implement a paging system. Paging is performed by using the following three properties of the MSQuery object:
BOOL includeTotalCount
NSInteger fetchLimit
NSInteger fetchOffset
In the following example, a simple function requests 20 records from the server and then appends them to the local collection of previously loaded records:
- (bool) loadResults() {
MSQuery *query = [self.table query];
query.includeTotalCount = YES;
query.fetchLimit = 20;
query.fetchOffset = self.loadedItems.count;
[query readWithCompletion:(NSArray *items, NSInteger totalCount, NSError *error) {
if(!error) {
//add the items to our local copy
[self.loadedItems addObjectsFromArray:items];
//set a flag to keep track if there are any additional records we need to load
self.moreResults = (self.loadedItems.count < totalCount);
}
}];
}
thanks for your help.
If you are getting Error " Expect Method Body " then you copied it into your code incorrectly and there is a formatting issue.
If you want to load data with paging in a single call, I would do something like this:
in your .h file declare
typedef void (^CompletionBlock) ();
#property (nonatomic, strong) NSMutableArray *results;
in your .m file
- (void)loadData
{
self.results = [[NSMutableArray alloc] init];
MSClient *client = [MSClient clientWithApplicationURLString:#"YOUR_URL" applicationKey:#"YOUR_KEY"]
MSTable *table = [client tableWithName:#"YOUR_TABLE"];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"YOUR_SELECT_FILTER"];
MSQuery *query = [[MSQuery alloc] initWithTable:table predicate:predicate];
//note the predicate is optional. If you want all rows skip the predicate
[self loadDataRecursiveForQuery:query withCompletion:^{
//do whatever you need to do once the data load is complete
}];
}
- (void)loadDataRecursiveForQuery:(MSQuery *)query withCompletion:(CompletionBlock)completion
{
query.includeTotalCount = YES;
query.fetchLimit = 1000; //note: you can adjust this to whatever amount is optimum
query.fetchOffset = self.results.count;
[query readWithCompletion:(NSArray *items, NSInteger totalCount, NSError *error) {
if(!error) {
//add the items to our local copy
[self.results addObjectsFromArray:items];
if (totalCount > [results count]) {
[self loadDataRecursiveForQuery:query withCompletion:completion];
} else {
completion();
}
}
}];
}
Note: I haven't tested this code, but it should work more or less.
Related
I'm trying out the "Sample Blog App" on Parse Server for iOS and cannot figure out what is the smartes way to fetch all child objects of another class (together with the parent objects).
The "Sample Blog App" (which creates automatically when you create a new account) contains the classes Comment and Post. The Comment class contains a relation to the Post class as shown below (from the dashboard), but there is no relation in the opposite direction.
Now, I want to fetch all posts and all the comments related to each post. The code below does that, but I'm assuming there must be a smarter way...? If you know how, please share. Thanks in advance!
- (void)fetchPosts {
NSString *commentsKey = #"comments";
NSString *postKey = #"post";
PFQuery *query = [PFQuery queryWithClassName:#"Comment"];
[query includeKey:postKey];
[query findObjectsInBackgroundWithBlock:^(NSArray * _Nullable objects, NSError * _Nullable error) {
if (error == nil) {
NSMutableArray *array = [[NSMutableArray alloc]init];
for (PFObject *comment in objects) {
PFObject *post = [comment objectForKey:postKey];
NSDictionary *existingPostDict = [[array filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"%K = %#", #"post.objectId", post.objectId]] firstObject];
if (existingPostDict) {
// update comments
NSArray *comments = [[existingPostDict objectForKey:commentsKey] arrayByAddingObject:comment];
// create new dictionary and replace the old one
NSDictionary *newPostDict = [[NSDictionary alloc]initWithObjectsAndKeys:[existingPostDict objectForKey:postKey], postKey, comments, commentsKey, nil];
[array replaceObjectAtIndex:[array indexOfObject:existingPostDict] withObject:newPostDict];
}
else {
// first post, create a new dict
NSDictionary *newPostDict = [[NSDictionary alloc]initWithObjectsAndKeys:post, postKey, #[comment], commentsKey, nil];
[array addObject:newPostDict];
}
}
self.posts = array; // assuming: #property (nonatomic, strong) NSArray *posts;
}
else {
NSLog(#"Error fetching posts: %#", error.localizedDescription);
}
}];
}
Instead of using include on your query you should use whereKey:equals: and pass the post object as the second argument. This will filter and return only the comment objects that contain that have that post as their value for post
One problem I see with your query is that there is a possibility this will not fetch every post in the database. If a post has 0 comments, none of the Comment objects will have a reference to it and thus you will not receive it.
Therefore you should actually do a query on "Post" and in its completion do a query on "Comment". This way you will not miss any posts with 0 comments. When you do this, you will not need to include the "post" key in the Comment query. This has multiple benefits.
First, each include is also another query for that object. So each new Comment object will create another query in the backend. You will get rid of this automatically.
Second, for a "Post" with multiple comments, you will be querying for the same post multiple times and that same post will be returned multiple times which consumes unnecessary bandwidth.
After getting Posts and Comments separately just combine them.
Apart from that I would do the combining like so which I find more readable but that is just personal preference.
- (void)fetchPosts {
NSString *commentsKey = #"comments";
NSString *postKey = #"post";
PFQuery *query = [PFQuery queryWithClassName:#"Comment"];
[query includeKey:postKey];
[query findObjectsInBackgroundWithBlock:^(NSArray * _Nullable objects, NSError * _Nullable error) {
if (error == nil) {
NSMutableArray *array = [[NSMutableArray alloc]init];
NSMutableDictionary *d = [NSMutableDictionary dictionary];
for (PFObject *comment in objects) {
PFObject *post = [comment objectForKey:postKey];
if (d[post.objectId]) {
[d[post.objectId][commentsKey] addObject:comment];
}
else{
d[post.objectId] = [NSMutableDictionary dictionary];
d[post.objectId][postKey]=post;
d[post.objectId][commentsKey] = [NSMutableArray arrayWithObject:comment];
}
}
for (NSString *key in [d allKeys]) {
[array addObject:d[key]];
}
self.posts = array; // assuming: #property (nonatomic, strong) NSArray *posts;
}
else {
NSLog(#"Error fetching posts: %#", error.localizedDescription);
}
}];
}
This is how I did it, using findObjectsInBackground together with continueWithSuccessBlock: methods (for better error handling one can choose continueWithBlock: instead):
- (void)fetchPosts {
/**
create "post" and "comment" queries and use a BFTask-method from
Bolts.framework to chain downloading tasks together (bundled with Parse SDK)
*/
NSMutableArray *posts = [NSMutableArray new];
PFQuery *postQuery = [PFQuery queryWithClassName:#"Post"];
[[[postQuery findObjectsInBackground] continueWithSuccessBlock:^id(BFTask * task) {
[posts addObjectsFromArray:task.result];
PFQuery *commentsQuery = [PFQuery queryWithClassName:#"Comment"];
return [commentsQuery findObjectsInBackground];
}] continueWithSuccessBlock:^id(BFTask * task) {
/**
loop through posts and filter out comments with the same objectId in post,
then create a dictionary with post and related comments. done! :)
*/
NSMutableArray *postsAndComments = [NSMutableArray new];
for (PFObject *post in posts) {
NSArray *comments = [task.result filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"%K == %#", #"post.objectId", post.objectId]];
[postsAndComments addObject:#{#"post":post, #"comments":comments}];
}
/**
note: BFTask-blocks not running in main thread!
*/
dispatch_async(dispatch_get_main_queue(), ^{
self.posts = postsAndComments; // assuming: #property (nonatomic, strong) NSArray *posts;
});
return nil;
}];
}
Apologies that I couldn't think of a better way to describe my
application’s functionality.
I've found quite a lot of posts to this topic, also in the old archive at parse.com. Nevertheless it just doesn't work for me. After creating an instance of a PFQuery that is triggered by PFQuery.findObjects (but runs on a background thread) I'm not able to cancel it during its request process.
Scenario : Basically I have an app which connects to Parse. I have display the data which is more than 100 records in DataDisplay Screen and it has a back button when user click on back button and if PFQuery.findObjects still run it on background thread then I have to cancel it.
I have tried inserting PFQuery.cancel in the viewWillDisappear, but it can not stop and due to these DataDisplay Screen’s dealloc method is not call.
My code, incase it may help:
- (void)loadANDSortingSongInformationWS {
if(ISINTERNET) {
if(self.isShowLoadingForSkipSong)//Not Showing the activity indicator
self.isShowLoadingForSkipSong = NO;
else if(self.isFirstLoad || self.isAddPullToRefreshLikeSong)//Showing the indicator
[self showHideLoading:YES];
PFQuery *query = [PFQuery queryWithClassName:[UserPlaylistSongs parseClassName]];
[query setLimit:1000];
[query orderByDescending:#"createdAt"];
[query whereKey:#"Playlist" equalTo:self.playlistInfo];
[query includeKey:#"Playlist"];
[query includeKey:#"Song"];
[query includeKey:#"AddedBy"];
[query includeKey:#"Host"];
__weak typeof(self) weakSelf = self;
[self.opearation addOperationWithBlock:^{
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (objects.count == 0) {//No Songs Found
//If there is no records
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf showHideLoading:NO];
if(weakSelf.isFirstLoad || weakSelf.isAddPullToRefreshLikeSong) {//Problem while user pull to refresh when there is no song
[KSToastView ks_showToast:#"No Songs Found." duration:1.0f];
}
});
}
else {//Songs Found
dispatch_async(dispatch_get_main_queue(), ^{
NSMutableArray *arrParseSongList = [[NSMutableArray alloc] init];
__block NSInteger getTotalObjectsCount = 0;
for(NSInteger i=0; i<objects.count; i++) {
SongListInfo *songListData = [[SongListInfo alloc] init];
songListData.userPlaylistInfo = objects[i];
songListData.totLikes = [objects[i][#"Likes"] integerValue];
songListData.totDisLikes = [objects[i][#"Dislikes"] integerValue];
songListData.isPlaying = [objects[i][#"PlayingStatus"] boolValue];
songListData.songInfo = objects[i][#"Song"];
songListData.hostInfo = objects[i][#"Host"];
songListData.addedInfo = objects[i][#"AddedBy"];
songListData.playlistInfo = objects[i][#"Playlist"];
songListData.alreadyPlayedOrder = [objects[i][#"AlreadyPlayedIndex"] integerValue];
songListData.totRating = songListData.totLikes - songListData.totDisLikes;
songListData.createdDate = songListData.userPlaylistInfo.createdAt;
//User Specific for loading the song list.
PFQuery *queryLikeDislike = [PFQuery queryWithClassName:[SongLikeDislike parseClassName]];
[queryLikeDislike whereKey:#"SongID" equalTo:songListData.songInfo.objectId];
[queryLikeDislike whereKey:#"UserID" equalTo:[SINGLETON getUserID]];
[queryLikeDislike whereKey:#"PlaylistID" equalTo:songListData.playlistInfo.objectId];
[queryLikeDislike findObjectsInBackgroundWithBlock:^(NSArray *objectsLike, NSError *error) {
getTotalObjectsCount += 1;
if(error == nil) {
if(objectsLike.count) {
BOOL isDelete = [objectsLike.lastObject[#"DeleteRecord"] boolValue];
BOOL isLike = [objectsLike.lastObject[#"Like"] boolValue];
if(isDelete)
songListData.ratingType = RATING_GRAY;
else if(isLike)
songListData.ratingType = RATING_GREEN;
else
songListData.ratingType = RATING_RED;
}
else
songListData.ratingType = RATING_GRAY;
}
else
NSLog(#"Problem while getting the rating type");
[arrParseSongList addObject:songListData];
NSLog(#"i : %ld, objects : %ld",(long)getTotalObjectsCount, (long)objects.count);
if(getTotalObjectsCount == objects.count)
[weakSelf processAfterGettingLikesAndDislikeInfo:arrParseSongList];
}];
}
});
}
}];
}];
NSLog(#"In method -> All operation : %ld",(long)self.opearation.operations.count);
}
else
[UIAlertView showErrorWithMessage:NO_INTERNET handler:nil];
}
- (void)processAfterGettingLikesAndDislikeInfo:(NSMutableArray *)arrParseSongList {
NSPredicate *filterGrayout = [NSPredicate predicateWithFormat:#"isPlaying == YES"];
NSArray *arrGrayOut = [arrParseSongList filteredArrayUsingPredicate:filterGrayout];
NSSortDescriptor *aSortDescriptorGrayedOut = [[NSSortDescriptor alloc] initWithKey:#"alreadyPlayedOrder.intValue" ascending:YES];
NSArray *arrGrayedOutSong = [NSMutableArray arrayWithArray:[arrGrayOut sortedArrayUsingDescriptors:[NSArray arrayWithObject:aSortDescriptorGrayedOut]]];
NSPredicate *filterNonPlay = [NSPredicate predicateWithFormat:#"isPlaying == NO"];
NSArray *arrNonPlay = [arrParseSongList filteredArrayUsingPredicate:filterNonPlay];
NSSortDescriptor *aSortDescriptorRating = [[NSSortDescriptor alloc] initWithKey:#"totRating.intValue" ascending:NO];
NSSortDescriptor *aSortDescriptorCreatedDate = [[NSSortDescriptor alloc] initWithKey:#"createdDate" ascending:YES];
NSArray *arrSortOnNormalSong = [NSMutableArray arrayWithArray:[arrNonPlay sortedArrayUsingDescriptors:[NSArray arrayWithObjects:aSortDescriptorRating,aSortDescriptorCreatedDate,nil]]];
if(self.arrSongsData.count)
[self.arrSongsData removeAllObjects];
[self.arrSongsData addObjectsFromArray:arrGrayedOutSong];
[self.arrSongsData addObjectsFromArray:arrSortOnNormalSong];
[self showHideLoading:NO];
[self.tblView reloadData];
}
And I am call in viewWillDisappear.
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
if(self.queryInstance)
[self.queryInstance cancel];
}
Thanks for any help!
So there are a number of things which are potentially keeping the instance in memory:
You're adding operations to an operation queue, and those operations are retaining contents so any operations still in the queue will keep the controller instance alive
Your completion blocks are all using self rather than creating a __weak reference and using that, so while the blocks are retained so it the controller instance
In the completion of the first query you're starting another query for each of the, potentially 1000, results. Not only could this flood the network with requests, but each one is also retaining the controller instance
You potentially have 1000 queries running that aren't cancelled, depending on exactly when you try to cancel
Mainly, you should be using weak references in the completion blocks so that when you try to deallocate the controller it disappears and the completions of the queries just silently run to nothing in the background. Obviously you do really want to cancel or prevent all 1000 queries from running if the user isn't interested though...
I have an NSArray called "malls" that contains a large number of NSDictionaries (each a specific mall) that I uploaded to Parse.com. I want my users to be able to access this information to create map annotations.
I've tried to do this in 2 different ways:
I tried uploading the entire array as a property of a single object:
this is the upload:
in the dataBank.h file:
#property (strong, nonatomic) NSMutableArray* malls;
in the .m file
PFObject *obj = [PFObject objectWithClassName:#"malls"];
obj[#"mallsData"] = self.malls;
[obj saveInBackground];
I try to get the data from parse:
-(NSMutableArray *)createAnnotationsFromParse
{
__block NSMutableArray* data = [[NSMutableArray alloc]init];
__block NSMutableArray* annots = [[NSMutableArray alloc]init];
PFQuery* query = [PFQuery queryWithClassName:#"malls"];
[query getObjectInBackgroundWithId:#"Eaib9yfTRe" block:^(PFObject *object, NSError *error) {
data = [object objectForKey:#"mallsData"];
annots = [self createAnnotations:data];
}];
return annots;
}
The problem is getObjectInBackground is asynchronous and always returns before getting the data from the server. I tried moving the "return annots" inside the code block but that gives the following error: "incompatible block pointer types".
I uploaded 5 "mall" objects to class "malls2". Each object has 2 properties- name and address:
for(int i = 0; i < 5; i++)
{
PFObject *mallsObj = [PFObject objectWithClassName:#"malls2"];
mallsObj[name] = [[self.malls objectAtIndex:i]objectForKey:name];
mallsObj[address] = [[self.malls objectAtIndex:i]objectForKey:address];
[mallsObj saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if(succeeded)
NSLog(#"yay");
else
NSLog(#"%#", error.description);
}];
}
then I try to get it back:
-(NSMutableArray *)createAnnotationsFromParse
{
__block Annotation* anno = [[Annotation alloc]init];
__block NSMutableArray* annots = [[NSMutableArray alloc]init];
PFQuery* query = [PFQuery queryWithClassName:#"malls2"];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if(error)
NSLog(#"%#", error.description);
else
{
for(int i = 0; i < [objects count]; i++)
{
//createAnnotationWithTitle is a func in a different class that creates the annotation
anno = [anno createAnnotationWithTitle:[[objects objectAtIndex:i] objectForKey:name] andAddress:[[objects objectAtIndex:i]objectForKey:address]];
}
[annots addObject:anno];
}
}];
return annots;
}
I get 5 objects but they're all empty.
It's a basic misunderstanding about asynchronous methods with block parameters. The trick is to get out of the habit of thinking that code that appears later in a source file runs later. The assumption works in this function:
- (void)regularFunction {
// these NSLogs run top to bottom
NSLog(#"first");
NSLog(#"second");
NSLog(#"third");
}
This will generate logs: first, second, third. Top to bottom, but not in this one:
- (void)functionThatMakesAsynchCall {
// these NSLogs do not run top to bottom
NSLog(#"first");
[someObject doSomeAsynchThing:^{
NSLog(#"second");
}];
NSLog(#"third");
}
That function will generate logs - first, third, second. The "second" NSLog will run well after the "third" one.
So what should you do? Don't try to update the UI with results of a parse call until after it completes, like this:
// declared void because we can't return anything useful
- (void)doSomeParseThing {
// if you change the UI here, change it to say: "we're busy calling parse"
PFQuery* query = [PFQuery queryWithClassName:#"malls2"];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if(!error) {
// change the UI here, say by setting the datasource to a UITableView
// equal to the objects block parameter
}
}];
// don't bother changing the UI here
// don't bother returning anything here
// we just started the request
}
But what if doSomeParseThing is really a model function, whose only job is to fetch from parse, not to know anything about UI? That's a very reasonable idea. To solve it, you need to build your model method the way parse built their's, with block parameter:
// in MyModel.m
// declared void because we can't return anything useful
+ (void)doSomeParseThing:(void (^)(NSArray *, NSError *))block {
PFQuery* query = [PFQuery queryWithClassName:#"malls2"];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
block(objects, error);
}];
}
Now your view controller can call, leave the query work to your model and the UI work to the vc:
// change UI to 'busy' here
[MyModel doSomeParseThing:^(NSArray *objects, NSError *error) {
// update UI with objects or error here
}];
Figured it out. It looked like I was getting "empty objects" (can be seen here postimg.org/image/ot7ehn29b ) but once I tried to access data from the objects I saw there was no problem. Basiclly I was tricked by the PFObjects in the array showing "0 objects" and assumed it meant they came back from Parse.com empty. Here's how I checked, just for reference:
PFQuery *query = [PFQuery queryWithClassName:#"malls2"];
NSArray *array = [query findObjects];
NSLog(#"%#", [[array objectAtIndex:0] objectForKey:#"name"]; // I have a string property called "name" in my Parse object.
I'm trying to initialize a couple properties fetched from parse.com when the view is loaded so I can do calculation with them. For instance, I declare the following in my header file:
TaskViewController.h
#property (nonatomic, assign) int taskTotalCount;
#property (nonatomic, assign) int taskCompletedCount;
#property (nonatomic, assign) int progressCount;
- (void)CountAndSetTotalTask;
- (void)CountAndSetCompletedCount;
- (void)CalculateProgress;
Then in the implementation, assuming all the other initialization are setup properly and they are called in viewdidload, below are the method implementations:
TaskViewController.m
- (void)CountAndSetCompletedCount {
// Query the tasks objects that are marked completed and count them
PFQuery *query = [PFQuery queryWithClassName:self.parseClassName];
[query whereKey:#"Goal" equalTo:self.tasks];
[query whereKey:#"completed" equalTo:[NSNumber numberWithBool:YES]];
[query countObjectsInBackgroundWithBlock:^(int count, NSError *error) {
if (!error) {
// The count request succeeded. Assign it to taskCompletedCount
self.taskCompletedCount = count;
NSLog(#"total completed tasks for this goal = %d", self.taskCompletedCount);
} else {
NSLog(#"Fail to retrieve task count");
}
}];
}
- (void)CountAndSetTotalTask {
// Count the number of total tasks for this goal
PFQuery *query = [PFQuery queryWithClassName:self.parseClassName];
[query whereKey:#"Goal" equalTo:self.tasks];
[query countObjectsInBackgroundWithBlock:^(int count, NSError *error) {
if (!error) {
// The count request succeeded. Assign it to taskTotalCount
self.taskTotalCount = count;
NSLog(#"total tasks for this goal = %d", self.taskTotalCount);
} else {
NSLog(#"Fail to retrieve task count");
}
}];
}
- (void)CalculateProgress {
int x = self.taskCompletedCount;
int y = self.taskTotalCount;
NSLog(#"the x value is %d", self.taskCompletedCount);
NSLog(#"the y value is %d", self.taskTotalCount);
if (!y==0) {
self.progressCount = ceil(x/y);
} else {
NSLog(#"one number is 0");
}
NSLog(#"The progress count is = %d", self.progressCount);
}
The issue I am encountering is that the taskTotalCount and taskCompletedCount are set correctly and returns different numbers in the first two methods while the NSLog returns 0 for both x and y. Therefore I'm not sure if the third method somehow got loaded before the two properties are set or it's some other issues. Thank you in advance for any pointers.
Assuming you call these three methods like this:
- (void)viewDidLoad {
[super viewDidLoad];
[self CountAndSetCompletedCount];
[self CountAndSetTotalTask];
[self CalculateProgress];
}
then the problem is that the first two methods return immediately while the calls to Parse occur in the background. This means that CalculateProgress is called long before you get back the results from the calls to Parse.
One solution is to just call CountAndSetCompletedCount from viewDidLoad. In its completion handler you then call CountAndSetTotalTask. In its completion handler you finally call CalculateProgress.
I am not figuring out how to perform a query in Azure. I did finally figure out inserts, but now I am trying to query from Azure. Two parts here, how do I return the result from Azure and how do I read the results in objective-C?
Thus far, I have this
-(double)GetValidAppVersion
{
// Create a proxy client for sending requests to the Azure platform.
MSClient *client = [MSClient clientWithApplicationURLString : #""
withApplicationKey : #"];
MSTable *appSettingsTable = [client getTable:#"AppSettings"];
NSPredicate * predicate = [NSPredicate predicateWithFormat:#"Key == AppVersion"];
NSArray *queryResults = [[NSArray alloc] init];
[appSettingsTable readWhere:predicate completion:^(NSArray *results, NSInteger totalCount, NSError *error)
{
self.items = [results mutableCopy];
}];
return 1.0;
}
I have not figured out the Azure side either. How can I query and return a result based on the input parameter?
My table is simple with
ID int
Key varchar
Value varchar
Any help with getting this going is greatly appreciated.
Edit:
I added this to my controller
-(bool) IsAppVersionValid
{
AppDelegate *delegate = [[UIApplication sharedApplication] delegate];
double validAppVersion = [delegate.appVersion doubleValue];
double serverAppVersion;
NSDictionary *item = #{ #"complete" : #(NO) };
[self.Service SelectAppVersion:item completion:^(NSUInteger index)
{
}];
return true;//clientVersion >= validAppVersion;
}
And this to my service (this is sloppy as it should be a simple completion block -- I would like to pass NSString * with the AppSettings key value and use that in the predicate as well. Any thoughts on the syntax for that?
typedef void (^CompletionWithAppVersionBlock)(NSUInteger index);
- (void) SelectAppVersion:(NSDictionary *) item
completion:() completion;
All of the read table read methods that are part of the iOS SDK for Mobile Services are asynchronous which means that you have to pass a completion block into them (as you're doing above where you're setting self.items = [results mutableCopy];) in order to then do something with the results they are fetching.
This means that in order to get the value you're looking for, you'll want to pass in a completion block into your GetValidAppVersion method. You can then pass the app version you're getting back to that block. So something like this:
-(void) GetValidAppVersion:(NSDictionary *)item completion:(CompletionWithVersion)completion
{
MSTable *appSettingsTable = [client getTable:#"AppSettings"];
NSPredicate * predicate = [NSPredicate predicateWithFormat:#"Key == AppVersion"];
NSArray *queryResults = [[NSArray alloc] init];
[appSettingsTable readWhere:predicate completion:^(NSArray *results, NSInteger totalCount, NSError *error)
{
completion([results objectAtIndex:0]);
}];
}
You would need to define the CompletionWithVersion as being a block with a parameter returned (the AppVersion). Take a look at the iOS quickstart application to see how the completion blocks are defined.