I have to improve the performance of an iOS app connected to Parse. So far I’ve checked that some findObject queries take too long, some of them even 10 seconds!
I’ll put the code of one of the worst cases and what I’ve tried so far:
+ (void) getAllEvents
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
PFQuery *timestampsQuery = [PFQuery queryWithClassName:@"TimeStamp"];
[timestampsQuery fromLocalDatastore];
[timestampsQuery whereKey:@"type" equalTo:@"event"];
// This query very often (not always) takes a couple of seconds
NSArray *timeStampObjects = [timestampsQuery findObjects];
NSArray *timeStamps = [self TransformPFObjectIntoTimeStamp:timeStampObjects];
PFQuery *query = [PFQuery queryWithClassName:@"Event"];
if ([self noNetworkConnection])
{
[query fromLocalDatastore];
}
else
{
[query whereKey:@"user" equalTo:[PFUser currentUser]];
}
[query includeKey:@"admin"];
// This query very often (not always) takes several seconds, 6, 7…even 10 seconds!
NSArray *objects = [query findObjects];
NSMutableArray *eventArray = [NSMutableArray arrayWithArray:[self TransformPFObjectIntoEvents:objects]];
NSDictionary* dict = [NSDictionary dictionaryWithObjectsAndKeys:eventArray,@"events",timeStamps,@"timestamps", nil];
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:GET_ALL_MY_EVENTS object:dict];
});
});
}
+ (NSArray *)TransformPFObjectIntoTimeStamp:(NSArray *)objects
{
NSMutableArray* timestamps = [[NSMutableArray alloc]init];
for (PFObject* object in objects)
{
TimeStamp* timeStamp = [[TimeStamp alloc] init];
timeStamp.parseObject = object;
[timestamps addObject:timeStamp];
}
return timestamps;
}
+ (NSMutableArray *)TransformPFObjectIntoEvents:(NSArray *)objects
{
[PFObject pinAllInBackground:objects withName:PFObjectDefaultPin];
NSMutableArray* eventArray = [[NSMutableArray alloc]init];
for (PFObject* object in objects)
{
Event* event = [[Event alloc] init];
event.parseObject = object;
PFInstallation *currentInstallation = [PFInstallation currentInstallation];
[currentInstallation addUniqueObject:object[@"channel"] forKey:@"channels"];
[currentInstallation saveInBackground];
if(event.parseObject[@"imageFile"] != [NSNull null])
{
PFFile *userImageFile = event.parseObject[@"imageFile"];
if (userImageFile != NULL)
{
UIImage *image = [UIImage imageWithData:[userImageFile getData]];
[event setAttributeImage:image];
}
}
[eventArray addObject:event];
}
return eventArray;
}
I have to add that the second call to findObjects sometimes gives me a “cfnetwork sslhandshake failed (-9824)” which is strange to me because the app has the info.plist with these keys (but I guess this should be in another question):
<key>NSTemporaryExceptionRequiresForwardSecrecy</key>
<false/>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
And another thing, table “event” has over 1000 registers.
What I’ve tried:
1) Comment [timestampsQuery fromLocalDatastore]; Same results, these queries take too long.
2) User findObjectInBackground:
+ (void) getAllMyEvents{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
_calls = 0;
PFQuery *timestampsQuery = [PFQuery queryWithClassName:@"TimeStamp"];
[timestampsQuery fromLocalDatastore];
[timestampsQuery whereKey:@"type" equalTo:@"event"];
[timestampsQuery findObjectsInBackgroundWithBlock:^(NSArray * _Nullable objects, NSError * _Nullable error) {
[self completeQueriesWithType:1 andObjects:objects];
}];
PFQuery *query = [PFQuery queryWithClassName:@"Event"];
if ([self noNetworkConnection])
{
[query fromLocalDatastore];
}
else
{
[query whereKey:@"user" equalTo:[PFUser currentUser]];
}
[query includeKey:@"admin"];
[query findObjectsInBackgroundWithBlock:^(NSArray * _Nullable objects, NSError * _Nullable error) {
[self completeQueriesWithType:2 andObjects:objects];
}] ;
});
}
+ (void) completeQueriesWithType:(int)type andObjects:(NSArray *)objs
{
_calls++;
NSArray *timeStamps = nil;
NSMutableArray *eventArray = nil;
if(type == 1)
{
timeStamps = [TimeStamp TransformPFObjectIntoTimeStamp:objs];
}
else if(type == 2)
{
eventArray = [NSMutableArray arrayWithArray:[self TransformPFObjectIntoEvents:objs]];
}
if(_calls == 2)
{
NSDictionary* dict = [NSDictionary dictionaryWithObjectsAndKeys:eventArray,@"events",timeStamps,@"timestamps", nil];
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:GET_ALL_MY_EVENTS object:dict];
});
}
}
But it is still taking seconds and I’ve got this error too: “Warning: A long-running operation is being executed on the main thread. Break on warnBlockingOperationOnMainThread() to debug.” Why? If I’m running this in the background.
Since I know Parse local database has some impact in performance I’ve though on not using it at all, remove all its appearances, but I would like to fix this using Parse local database if it’s possible.
So…why are these queries taking so many time?
EDIT: Following @Wain suggestion I've ended with this test:
+ (void) getAllEvents
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"Running only second of findObjects");
PFQuery *query = [PFQuery queryWithClassName:@"Event"];
if ([self noNetworkConnection])
{
NSLog(@"Reading from local datastore");
[query fromLocalDatastore];
}
else
{
[query whereKey:@"user" equalTo:[PFUser currentUser]];
}
[query includeKey:@"admin"];
// This query very often (not always) takes several seconds, 6, 7…even 10 seconds!
NSLog(@"Just before findObjects");
NSArray *objects = [query findObjects];
NSLog(@"End of running only second of findObjects");
});
}
With these results:
2016-03-31 11:15:29.295 MyApp[...] Running only second of findObjects
2016-03-31 11:15:29.318 MyApp[...] Just before findObjects
2016-03-31 11:15:38.025 MyApp[...] End of running only second of findObjects
Only this method NSArray *objects = [query findObjects]; takes 9 senconds!
EDIT 2 I'm not sure if this is important, but one of the fields in the "event" table is a PFFile (an image). Could this make the find query slower?