I have the following code where I am calling self.tableView reloadData inside the loop which is a bad idea. If I put it outside the loop then it would not work as expected since it will be called before updating the noOfGroceryItems field.
How can I improve it?
-(void) populateShoppingLists {
[_groceriesService getAllShoppingLists:^(NSArray *results, NSError *error) {
if(error) {
NSLog(#"%#",error.localizedDescription);
}
for(CKRecord *record in results) {
ShoppingList *shoppingList = [[ShoppingList alloc] initWithRecord:record];
// get the number of grocery items in the shopping list
[_groceriesService getItemsByShoppingList:shoppingList result:^(NSArray *results, NSError *error) {
shoppingList.noOfGroceryItems = results.count;
[_shoppingLists addObject:shoppingList];
// THIS IS BAD IDEA
**dispatch_async(dispatch_get_main_queue(), ^{
_shoppingLists = [_shoppingLists sort:#"title" ascending:YES];
[self.tableView reloadData];
});**
}];
}
}];
}
Implementation for getItemsShoppingList method:
-(void) getItemsByShoppingList:(ShoppingList *)shoppingList result:(GetItemsByShoppingList) getItemsByShoppingList
{
CKQuery *query = [[CKQuery alloc] initWithRecordType:#"GroceryItems" predicate:[NSPredicate predicateWithFormat:#"ShoppingList == %#",shoppingList.record]];
[_privateDB performQuery:query inZoneWithID:nil completionHandler:^(NSArray *results, NSError *error) {
getItemsByShoppingList(results,error);
}];
}
You could add a conditional to reload the table if and only if the last shopping list has been fetched, ex:
[_groceriesService getAllShoppingLists:^(NSArray *results, NSError *error) {
if(error) {
NSLog(#"%#",error.localizedDescription);
}
// Variable to count the number
// of records processed
__block int recordsProcessed = 0;
for(CKRecord *record in results) {
ShoppingList *shoppingList = [[ShoppingList alloc] initWithRecord:record];
// (I've changed this second results variable from results
// to results2 in order to distinguish between the two
// "results" variables.)
// get the number of grocery items in the shopping list
[_groceriesService getItemsByShoppingList:shoppingList
result:^(NSArray *results2, NSError *error) {
shoppingList.noOfGroceryItems = results2.count;
[_shoppingLists addObject:shoppingList];
dispatch_async(dispatch_get_main_queue(), ^{
_shoppingLists = [_shoppingLists sort:#"title" ascending:YES];
// Increment recordsProcessed to indicate another
// record has been processed
recordsProcessed ++;
// If all the records have been processed,
// reload the table (using the outer block's
// results variable, not the inner block's result2
// variable).
if (recordsProcessed == results.count) {
[self.tableView reloadData];
}
});
}];
}
}];
Update: And a second solution. Assuming that _shoppingLists starts out empty, you can simply compare results with the number of elements put into _shoppingLists, ex:
[_groceriesService getAllShoppingLists:^(NSArray *results, NSError *error) {
if(error) {
NSLog(#"%#",error.localizedDescription);
}
for(CKRecord *record in results) {
ShoppingList *shoppingList = [[ShoppingList alloc] initWithRecord:record];
// (I've changed this second results variable from results
// to results2 in order to distinguish between the two
// "results" variables.)
// get the number of grocery items in the shopping list
[_groceriesService getItemsByShoppingList:shoppingList
result:^(NSArray *results2, NSError *error) {
shoppingList.noOfGroceryItems = results2.count;
[_shoppingLists addObject:shoppingList];
dispatch_async(dispatch_get_main_queue(), ^{
_shoppingLists = [_shoppingLists sort:#"title" ascending:YES];
// If the number of shopping lists stored
// equals the number of records processed,
// reload the table (using the outer block's
// results variable, not the inner block's result2
// variable).
if (_shoppingLists.count == results.count) {
[self.tableView reloadData];
}
});
}];
}
}];
If you put a dispatch_async after the for loop, you're guaranteed that the block will be dispatched on the main queue, after the loops completes (and your data is updated).
Why not just dispatch the reloadData call once after all your model are processed and updated?
Instead of fast enumerating the results array, you could enumerate with a block, that gives you the index of the current object. if the index is [results count] - 1, trigger the reload.
Of course you should not name two arrays results if the inner one does hide the outer, especially if you intend to call other peoples code «not a very good solution.»
Related
I want to fetch data from server with multiple calls inside for loop. I'm passing different parameter each time. I know it is possible to fetch data like, I'm fetching in code below :
for (NSDictionary *feedItem in [feed objectForKey:#"content"]) {
// url with feedItem data.
NSURL *url = ....
[UrlMethod GetURL:url success:^(NSDictionary *placeData) {
if (placeData) {
dispatch_async(dispatch_get_main_queue(), ^{
// adding object to table data source array
[dataSourceArray addObject:[placeData objectForKey:#"data"]];
// reloading table view.
[self.tableView reloadData];
});
}
} failure:^(NSError *error) {
}];
}
The problem is, Whenever I add data to dataSourceArry, It is not adding sequentially. It is adding according to response of API calls. Please let me know, If it is not clear.
In your case, I would allocate a mutable array first and set [NSNull null] at each position:
NSInteger count = [[feed objectForKey:#"content"] count];
NSMutableArray *dataSourceArray = [NSMutableArray arrayWithCapacity:count];
for (NSInteger i = 0; i < count; ++i) {
[dataSourceArray addObject:[NSNull null]];
}
Then, I would use something called dispatch groups (see more here http://commandshift.co.uk/blog/2014/03/19/using-dispatch-groups-to-wait-for-multiple-web-services/):
__block NSError *apiCallError = nil; // Just to keep track if there was at least one API call error
NSInteger index = 0;
// Create the dispatch group
dispatch_group_t serviceGroup = dispatch_group_create();
for (NSDictionary *feedItem in [feed objectForKey:#"content"]) {
// Start a new service call
dispatch_group_enter(serviceGroup);
// url with feedItem data.
NSURL *url = ...
[UrlMethod GetURL:url success:^(NSDictionary *placeData) {
if (placeData) {
dispatch_async(dispatch_get_main_queue(), ^{
// Add data to Data Source
// index should be the correct one, as the completion block will contain a snapshot of the corresponding value of index
dataSourceArray[index] = [placeData objectForKey:#"data"];
}
dispatch_group_leave(serviceGroup);
} failure:^(NSError *error) {
apiCallError = error;
dispatch_group_leave(serviceGroup);
}];
index++;
}
dispatch_group_notify(serviceGroup, dispatch_get_main_queue(),^{
if (apiCallError) {
// Make sure the Data Source contains no [NSNull null] anymore
[dataSourceArray removeObjectIdenticalTo:[NSNull null]];
}
// Reload Table View
[self.tableView reloadData];
});
Hope it works for you.
This might be of help for you,
//keep dictionary property which will store responses
NSMutableDictionary *storeResponses = [[NSMutableDictionary alloc]init];
//Somewhere outside function keep count or for loop
NSInteger count = 0;
for (NSDictionary *feedItem in [feed objectForKey:#"content"]) {
//Find out index of feddItem
NSInteger indexOfFeedItem = [[feed objectForKey:#"content"] indexOfObject:feedItem];
NSString *keyToStoreResponse = [NSString stringWithFormat:#"%d",indexOfFeedItem];
// url with feedItem data.
NSURL *url = ....
[UrlMethod GetURL:url success:^(NSDictionary *placeData) {
if (placeData) {
//instead of storing directly to array like below
// adding object to table data source array
[dataSourceArray addObject:[placeData objectForKey:#"data"]];
//follow this
//increase count
count++;
[storeResponses setObject:[placeData objectForKey:#"data"] forKey:keyToStoreResponse];
// reloading table view.
if(count == [feed objectForKey:#"content"].count)
{
NSMutableArray *keys = [[storeResponses allKeys] mutableCopy]; //or AllKeys
//sort this array using sort descriptor
//after sorting "keys"
for (NSString *key in keys)
{
//add them serially
[dataSourceArray addObject:[storeResponses objectForKey:key]];
}
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
}
}
} failure:^(NSError *error) {
}];
}
Edit : The answer I have given is directly written here,you might face compilation errors while actually running this code
Don't reload your table each time in the loop. After the loop finishes fetching data , do a sorting on your datasourcearray to get the desired result and then reload table.
This is because you're calling web-services asynchronously so it's not give guarantee that it's give response in sequence as you have made request!
Now solutions for that :
You should write your api like it's give all data at a time. So,
You not need to make many network call and it will improve
performance also!
Second thing you can make recursive kind of function, I mean make another request from completion handler of previous one. In this case once you got response then only another request will be initialize but in this case you will have to compromise with performance!! So first solution is better according to me!
Another thing you can sort your array after you get all the responses and then you can reload your tableView
Try the following :-
for (NSDictionary *feedItem in [feed objectForKey:#"content"]) {
// url with feedItem data.
NSURL *url = ....
[UrlMethod GetURL:url success:^(NSDictionary *placeData) {
if (placeData) {
// adding object to table data source array
[dataSourceArray addObject:[placeData objectForKey:#"data"]];
// reloading table view.
dispatch_sync(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
});
} failure:^(NSError *error) {
}];
}
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 using Parse and have a class of a few jobs with a rating (number 1 out of 5). I want to query for the class and stick each rating into an array, then calculate the average. However, when I try to add the objects into the array, it seems to only add the last item returned by the query, and I can't figure out why.
- (void)viewDidLoad {
[super viewDidLoad];
self.jobName = [self.job objectForKey:#"jobTitle"]; //jobName string
self.title = [NSString stringWithFormat:#"%#", self.jobName];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
PFQuery *getCompletedJobsQuery = [PFQuery queryWithClassName:#"completedJobs"];
[getCompletedJobsQuery findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
self.completedJobs = objects;
for (PFObject *completedJob in objects) {
self.ratingsArray = [[NSMutableArray alloc] init];
NSLog(#"RATING: %#", [completedJob objectForKey:#"customerRating"]);
[self.ratingsArray addObject:[completedJob objectForKey:#"customerRating"]];
}
NSLog(#"ratings array ... %#", self.ratingsArray);
[self.tableView reloadData];
} else {
NSLog(#"Error: %# %#", error, [error userInfo]);
}
}];
}
When it runs, it logs each rating, but when I try to print out the array or check its length, it only shows the last object and doesn't include the other objects:
2015-01-05 13:05:00.274 Ribbit[15538:251863] RATING: 3
2015-01-05 13:05:00.274 Ribbit[15538:251863] RATING: 5
2015-01-05 13:05:00.274 Ribbit[15538:251863] RATING: 4
2015-01-05 13:05:00.274 Ribbit[15538:251863] RATING: 5
2015-01-05 13:05:00.275 Ribbit[15538:251863] ratings array ... (
5
)
So, what's the proper way to retrieve things from Parse and put them into an array?
Edit: As oltman and Jack have indicated, I am re-creating the array each time the loop runs, so moving the array creation outside of the loop solves the problem. That is also my queue to take a break from coding for a few hours and sit in my cube of shame. Thanks!
You're creating a new array (self.ratingsArray = [[NSMutableArray alloc] init];) with each loop iteration. Move this line out of the loop (to before the loop) and you should come out of it with more than just the last object in the array.
You are recreating your array every iteration of the loop:
for (PFObject *completedJob in objects) {
self.ratingsArray = [[NSMutableArray alloc] init]; // HERE
NSLog(#"RATING: %#", [completedJob objectForKey:#"customerRating"]);
[self.ratingsArray addObject:[completedJob objectForKey:#"customerRating"]];
}
Move that outside of the loop:
self.ratingsArray = [[NSMutableArray alloc] init]; // HERE
for (PFObject *completedJob in objects) {
NSLog(#"RATING: %#", [completedJob objectForKey:#"customerRating"]);
[self.ratingsArray addObject:[completedJob objectForKey:#"customerRating"]];
}
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.