I'm triying to access each item on an NSArray trough enumerateObjectsUsingBlock, since it let me use fast enumration and evaluating the index.
When I use findObjectsInBackgroundWithBlock,
I get Warning: A long-running operation is being executed on the main
thread. Break on warnBlockingOperationOnMainThread() to debug.
As I thought was used in the background to not block the Mainthread. Here is my code and what I'm triying to achieve its that I have two UIImageView container that I'm pulling the images from the result of the relation on that query. Since there are only container I tought it was better just to evaluate the index of NSArray.
Not sure how I can remedy that warning.
Thanks
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (objects != 0) {
[objects enumerateObjectsUsingBlock:^(PFUser *object, NSUInteger idx, BOOL *stop) {
if (idx == 0) {
PFFile *userProfile = [object objectForKey:#"userPic"];
cell.promoter1.file = userProfile;
cell.promoter1.contentMode = UIViewContentModeScaleAspectFit;
[cell.promoter1 loadInBackground];
cell.promoter1Name.textAlignment = NSTextAlignmentCenter;
cell.promoter1Name.lineBreakMode = NSLineBreakByWordWrapping;
}
if (idx == 1) {
PFFile *userProfile = [object objectForKey:#"userPic"];
cell.promoter2.file = userProfile;
cell.promoter2.contentMode = UIViewContentModeScaleAspectFit;
[cell.promoter2 loadInBackground];
NSAttributedString *promoter1Name;
cell.promoter2Name.textAlignment = NSTextAlignmentCenter;
*stop = YES;
}
}];
}
}];
Troubleshooting my code, I realized that findObjectsInBackgroundWithBlock does not cause this warning.
On another part of my code I had this:
PFUser *user = [PFQuery getUserObjectWithId:#"ebwFrl8PcF"];
[relation addObject:user];
[self.event saveInBackground];
Which block the main thread.
I apologize.
Related
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 am building an app which will query a set of images (thumbnails) from the parse server and then show them in a collection view, similar to how is done on instagram in the users profile page. I created a method which queries the data from the backend successfully :
-(void)queryForTable {
PFQuery *query = [PFQuery queryWithClassName:#"VideoApp"];
NSString * author = [[PFUser currentUser] objectForKey:#"FBName"];
[query whereKey:#"author" equalTo:author];
[query orderByAscending:#"createdAt"];
[query setCachePolicy:kPFCachePolicyNetworkOnly];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
NSLog(#"Successfully retrieved %d objects", objects.count);
[self.collectionView reloadData];
userVideosArray = [[NSMutableArray alloc] initWithCapacity:objects.count];
for (PFObject *object in objects) {
PFFile *thumbnail = [object objectForKey:#"video_thumbnail"];
[thumbnail getDataInBackgroundWithBlock:^(NSData *data, NSError *error) {
if (!error) {
NSLog(#"Fetching image");
[userVideosArray addObject:[UIImage imageWithData:data]];
} else {
NSLog(#"Error: %# %#", error, [error userInfo]);
}
}];
}
}
}];
}
This method successfully retrieves four objects from the back end, and is called in the ViewDidLoad method.
Then in the collection view cellForRowAtIndexPath method I try to set the queried objects images to the UIImageview on the collectionviewcell as follows:
-(UICollectionViewCell*)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
//CollectionViewcellCollectionViewCell * cell = (CollectionViewcellCollectionViewCell*)[collectionView dequeueReusableCellWithReuseIdentifier:#"Cell" forIndexPath:indexPath];
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"Cell" forIndexPath:indexPath];
UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 70, 70)];
imageView.backgroundColor = [UIColor grayColor];
[cell addSubview:imageView];
PFQuery *query = [PFQuery queryWithClassName:#"VideoApp"];
NSString * info = [[PFUser currentUser] objectForKey:#"FBName"];
[query whereKey:#"author" equalTo:info];
imageView.image = [UIImage imageWithData:[userVideosArray objectAtIndex:indexPath.row]];
return cell;
}
I keep getting an NSException on imageView.image = [UIImage imageWithData:[userVideosArray objectAtIndex:indexPath.row]]; . Not 100 percent sure why. Any ideas?
You are doing this:
1) reloadData: wrong because you did not update any array before doing this. I assume that it is side effect of findObjectsInBackgroundWithBlock which is definitely wrong, because only object which performs reloadData should be responsible for updating data for datasource.
2) initializing userVideosArray with no items (userVideosArray.count == 0). Looking at your error and knowing that cellForItemAtIndexPath is invoked I'm assuming that -collectionView:numberOfItemsInSection: uses other different array to tell the number of items which is wrong, because you are trying to get item from userVideosArray which may have different number of items
3) filling userVideosArray with items in background. Keeping in mind 1 and 2 gives us an answer to your crash. in cellForItemAtIndexPath you are trying to obtain item which is still not loaded
Btw: [cell addSubview:imageView]; will keep adding image views to your cell each time collection view will reuse it
You should update collectionView after you have handled received data, not before. Your call to [self.collectionView reloadData] trickers calls to cellForItemAtIndexPath and my guess is that userVideosArray does not yet contain as many items as you're expecting.
Anyways, the crash. Try this to prevent crashing:
if (indexPath.item < userVideosArray.count)
imageView.image = [UIImage imageWithData:userVideosArray[indexPath.item]];
Btw when using collectionView, I'd recommend using item instead of row, since one collectionView row might contain several items. You know now what you're doing, and it's ok to use row, but later row vs. item terms might get confusing.
I am making an app that registers users and allows them to add friends etc. So I have a LoginViewController where I retrieve the array of user's friends' objectIds when the login is successful. This function is called.
- (void) getFriendList
{
NSString * objectID = [PFUser currentUser].objectId;
NSLog(#"%#", objectID);
PFQuery *query = [PFUser query];
[query getObjectInBackgroundWithId:objectID block:^(PFObject *username, NSError *error) {
sharedClass.sharedInstance->retrievedFriends = username[#"friendsIds"];
friendListLoaded = YES;
[self getFriendsUsernames];
}];
Here i get an array object that contains object ids of all the friends this user has. As you can see when getFriendList is completed, it calls another function called GetFriendsUsernames. This function is supposed to retrieve profile pictures and usernames of those friends so I can populate the Friend List view later.
-(void) getFriendsUsernames
{
NSMutableArray * objectIDs = [[NSMutableArray alloc] initWithArray: sharedClass.sharedInstance->retrievedFriends];
PFQuery *query = [PFUser query];
int friendsCount = [objectIDs count];
for(int i = 0; i<=friendsCount;i++)
{
[query getObjectInBackgroundWithId:objectIDs[i] block:^(PFObject *username, NSError *error) {
[sharedClass.sharedInstance->friendsUsernames addObject:username[#"username"]];
[sharedClass.sharedInstance->friendsProfilePictures addObject:username[#"ProfilePicture"]];
}];
NSLog(#"%#", sharedClass.sharedInstance->friendsUsernames );
}
}
But this seems to be unsuccessful because nothing is logged on the console where it should log username of retrieved friend whenever one query gets finished. Am I doing this the right way? My array count is right so loop runs to the number of friends a user has. It also prints the username of first object in objectIds array if i put the logging command in the loop.
Your NSLog runs immediately after your query objects are queued for execution. Therefore, even if the code is running correctly (and I suspect it might not be), you'll never get the correct results you're after logged to the console.
If you have your Parse classes designed in a certain way, you could collapse this into 1 query by using the include method on PFQuery. This assumes that you've created an array of Pointers on your User object, named "friends". If you actually store the objectId (i.e., the string value) of each friend, this code won't work.
I'm not using Swift yet, so here's how I'd write this query in Objective-C:
- (void)getFriendList {
PFUser *currentUser = [PFUser currentUser];
PFQuery *query = [PFUser query];
[query whereKey:#"objectId" equalTo:currentUser.objectId];
[query includeKey:#"friends.username"];
[query includeKey:#"friends.ProfilePicture"];
[query getFirstObjectInBackgroundWithBlock:^(PFObject *user, NSError *error) {
if (error != nil) {
// Process the error
} else }
// You've got your user and the data you wanted
}
}];
}
Found it. Not the prettiest way out there but well it does what i want. gets me arrays of usernames and profile pictures of every objectID contained in array of friends.
NSMutableArray * objectIDs = [[NSMutableArray alloc] initWithArray: sharedClass.sharedInstance->retrievedFriends];
PFQuery *query = [PFUser query];
PFFile * imageFile;
//UIImage *profilePictureData;
int friendsCount = [objectIDs count];
for(int i = 0; i<friendsCount;i++)
{
PFObject * username = [query getObjectWithId:objectIDs[i]];
[sharedClass.sharedInstance->friendsUsernames addObject:username[#"username"]];
[sharedClass.sharedInstance->friendsEmailAdresses addObject:username[#"email"]];
//NSLog(#"%#",username[#"ProfilePicture"]);
imageFile = [username objectForKey:#"ProfilePicture"];
NSData *imageData = [imageFile getData];
UIImage *imageFromData = [UIImage imageWithData:imageData];
[sharedClass.sharedInstance->friendsProfilePictures addObject:imageFromData];
NSLog(#"%#", sharedClass.sharedInstance->friendsUsernames );
}
I have a Parse query that runs to gather the 10 closest Arcades in your area, and I am trying to have them display those object titles in 10 separate labels. I have the following code which gathers the 10 closest and logs them, and I am trying to start by displaying the objectId in the labels but cannot figure out how to display them all and not just 1. Any suggestions?
PFQuery *query = [PFQuery queryWithClassName:#"Arcade"];
CLLocation *currentLocation = locationManager.location;
PFGeoPoint *userLocation =
[PFGeoPoint geoPointWithLatitude:currentLocation.coordinate.latitude
longitude:currentLocation.coordinate.longitude];
query.limit = 10;
[query whereKey:kPAWParseLocationKey nearGeoPoint:userLocation withinMiles:kPAWWallPostMaximumSearchDistance];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
// The find succeeded.
NSLog(#"Successfully retrieved %d scores.", objects.count);
// Do something with the found objects
for (PFObject *object in objects) {
NSLog(#"%#", object.objectId);
NSString *EventTitle = object.objectId;
EventTitle1.text = EventTitle;
for (UIImageView *imageView in self.imageViews) {
__block UIImage *MyPicture = [[UIImage alloc]init];
PFFile *imageFile = [object objectForKey:#"test"];
[imageFile getDataInBackgroundWithBlock:^(NSData *data, NSError *error){
if (!error) {
MyPicture = [UIImage imageWithData:data];
imageView.image = MyPicture;
}
}];
}
for (UILabel *EventLabel in self.EventTitles){
EventLabel.text = object.objectId;
}
}
UPDATE: I have created two collection outlets, however when they display they only display the final object queried, not all 10 of them? Am I doing something wrong?
Your problem is EventTitle1.text = EventTitle;, because you explicitly reference the label. What you should be doing is updating the labels in sequence. This could be done by having the labels in an array (perhaps an IBOutletCollection) and using the iteration index. Or you could tag all of the labels and then look them up (again, using the iteration index).
But, your intended solution isn't simple and doesn't scale. It would be better to use a table view (Parse SDK even gives you an easy way to populate a table view from a query).