Search code examples
iosparse-platformpfquery

Cancel PFQuery takes too much time to load


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!


Solution

  • So there are a number of things which are potentially keeping the instance in memory:

    1. 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
    2. 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
    3. 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
    4. 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...