I'm new to iOS development and I'm having trouble figuring out how to efficiently load my app's data at launch time. My app consists of a UITableView that is populated with a list of songs. Each cell displays data about the song: current user rating(if rated) and avg. user rating (avg. rating of all users). I'm using Parse for my backend data storage.
At launch time, my app runs two queries. The first query fetches songs to populate the table. It then calculates the avg. user rating (my backend schema includes count objects: rating1_count, rating2_count, etc. which are incremented when the user rates a song). It also orders the songs into separate data model arrays: topTracksOfTheWeek, topTracksOfTheMonth, topTracksAllTime.
The second query fetches all of the rated songs by the current user. It then compares this query to the first query to embed the user rating data into the first query to form the app's data model.
I'd like to limit the first query to 50 songs to optimize load time. However, with my current data model, I need to query all of the songs in my database to calculate all of the avg. user ratings and then order the songs into the topTracks arrays.
The only solution I can come up with is running a server-side script periodically to calculate the avg. user ratings. I would have to create a new class in my database for the topTracks arrays that are ordered by the script. In my app, I would do a lazily do a third query for the topTracks arrays.
Here is my data model code:
- (void)getDataSource
{
PFUser *user = [PFUser currentUser];
NSMutableArray *ratings;
if (user) {
PFQuery *queryRatings = [PFQuery queryWithClassName:@"Ratings"];
[queryRatings whereKey: @"user" equalTo: user.username];
[queryRatings orderByDescending:@"createdAt"];
queryRatings.limit = 1000;
ratings = [NSMutableArray arrayWithArray:[queryRatings findObjects]];
}
else
[self displayLoginViewController];
PFQuery *query = [PFQuery queryWithClassName:@"HotSongs"];
[query orderByDescending:@"createdAt"];
query.limit = 50;
_parseTracksArray = [NSMutableArray arrayWithArray:[query findObjects]];
if (!_favoriteTracks)
_favoriteTracks = [[NSMutableArray alloc] init];
else
[_favoriteTracks removeAllObjects];
if (!_topTracksOfTheWeek)
_topTracksOfTheWeek = [[NSMutableArray alloc] init];
else
[_topTracksOfTheWeek removeAllObjects];
if (!_topTracksOfTheMonth)
_topTracksOfTheMonth = [[NSMutableArray alloc] init];
else
[_topTracksOfTheMonth removeAllObjects];
if (!_topTracksOfTheYear)
_topTracksOfTheYear = [[NSMutableArray alloc] init];
else
[_topTracksOfTheYear removeAllObjects];
//Create NSDate objects for Top Charts comparison
NSDate *dateOfWeekFromNow = [NSDate dateWithTimeIntervalSinceNow:-604800]; // (60*60*24*7) in seconds
NSDate *dateOfMonthFromNow = [NSDate dateWithTimeIntervalSinceNow:-2592000]; // (60*60*24*30)
NSDate *dateOfYearFromNow = [NSDate dateWithTimeIntervalSinceNow:-31536000]; // (60*60*24*365)
NSInteger i, a;
for (a = 0; a < _parseTracksArray.count; a++) {
NSMutableDictionary *dictionary = [_parseTracksArray objectAtIndex:a];
NSString *trackId = [dictionary objectForKey:@"track_id"];
NSNumber *rating_1 = @([[dictionary objectForKey:@"rating_1"] floatValue]);
NSNumber *rating_2 = @([[dictionary objectForKey:@"rating_2"] floatValue]);
NSNumber *rating_3 = @([[dictionary objectForKey:@"rating_3"] floatValue]);
NSNumber *rating_4 = @([[dictionary objectForKey:@"rating_4"] floatValue]);
NSNumber *rating_5 = @([[dictionary objectForKey:@"rating_5"] floatValue]);
float totalRatingCount = rating_1.floatValue + rating_2.floatValue + rating_3.floatValue + rating_4.floatValue + rating_5.floatValue;
if (totalRatingCount > 0) {
float avgRating = (rating_1.floatValue + rating_2.floatValue*2 + rating_3.floatValue*3 + rating_4.floatValue*4 + rating_5.floatValue*5) / totalRatingCount;
[dictionary setObject:@(avgRating) forKey:@"avgRating"];
double confidenceRating = [self calculateConfidenceRating:avgRating basedOn:totalRatingCount];
[dictionary setObject:@(confidenceRating) forKey:@"confidenceRating"];
}
BOOL isRatedHigh = NO;
NSNumber *userRating = @0;
if (user) {
for (i = 0; i < ratings.count; i++) {
NSDictionary *dictionaryRatings = [ratings objectAtIndex:i];
NSString *ratedTrackId = [dictionaryRatings objectForKey:@"track_id"];
if ([trackId isEqualToString:ratedTrackId]) {
userRating = [dictionaryRatings objectForKey:@"userRating"];
if (userRating.intValue > 3) {
isRatedHigh = YES;
}
}
}
}
[dictionary setObject:userRating forKey:@"userRating"];
if (isRatedHigh == YES) {
[_favoriteTracks addObject:dictionary];
}
//Set createdAt object
PFObject *object = [_parseTracksArray objectAtIndex:a];
NSDate *dateTrackWasAddedToDatabase = [object createdAt];
if ([dateTrackWasAddedToDatabase compare:dateOfYearFromNow] == NSOrderedDescending) {
[_topTracksOfTheYear addObject:dictionary];
if ([dateTrackWasAddedToDatabase compare:dateOfMonthFromNow] == NSOrderedDescending) {
[_topTracksOfTheMonth addObject:dictionary];
if ([dateTrackWasAddedToDatabase compare:dateOfWeekFromNow] == NSOrderedDescending) {
[_topTracksOfTheWeek addObject:dictionary];
}
}
}
}
[self assignDataSource];
}
- (double)calculateConfidenceRating: (float)avgRating basedOn: (float)voteCount
{
const double baseConfidenceRating = 3;
const double baseConfidence = log10(CONFIDENT_NUMBER_OF_VOTES);
double confidence = log10( 1 + voteCount ) / baseConfidence;
double confidenceWeight = fmin( confidence, 1.0 );
double confidenceRating = (1.0 - confidenceWeight) * baseConfidenceRating + confidenceWeight * avgRating;
return confidenceRating;
}
- (void)assignDataSource
{
switch (_dataSourceAssignment.intValue) {
case 0: _dataSource = [NSMutableArray arrayWithArray:_parseTracksArray]; break;
case 1: _dataSource = [NSMutableArray arrayWithArray:_favoriteTracks]; break;
case 2: _dataSource = [NSMutableArray arrayWithArray:_topTracksOfTheWeek]; break;
case 3: _dataSource = [NSMutableArray arrayWithArray:_topTracksOfTheMonth]; break;
case 4: _dataSource = [NSMutableArray arrayWithArray:_topTracksOfTheYear]; break;
default: break;
}
if (_dataSourceAssignment.intValue >= 2) {
NSSortDescriptor *avgRatingSortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"confidenceRating" ascending:NO];
NSArray *sortDescriptors = @[avgRatingSortDescriptor];
NSArray *sortedArray = [_dataSource sortedArrayUsingDescriptors:sortDescriptors];
_dataSource = [NSMutableArray arrayWithArray:sortedArray];
}
// Filter genres
NSMutableArray *selectedGenresByUser = [[NSUserDefaults standardUserDefaults] objectForKey:@"selectedGenres"];
NSPredicate *predicate = [NSPredicate predicateWithFormat: @"trackGenre in $SELECTED_GENRES"];
predicate = [predicate predicateWithSubstitutionVariables:@{@"SELECTED_GENRES": [NSArray arrayWithArray:selectedGenresByUser]}];
NSArray *filtered = [NSArray arrayWithArray:[_dataSource filteredArrayUsingPredicate: predicate]];
_dataSource = [NSMutableArray arrayWithArray:filtered];
[self.tableView reloadData];
}