I have some methods which take some time to process to fetch data, perform calculations and then the return the result, for instance this instance method returns an array based on three parameters:
-(NSArray*)periodsForCompanies:(NSArray*)companies figureType:(NSString*)figureType
actualValuesOnly:(BOOL)actualValuesOnly
{.. };
As this method could be called many times with the same parameters from the same class and takes some time to finish, I would like to optimize my code in order to avoid that the code for this method is fully executed each time.
How could I store the previous parameters (and/or results) inside the method to be able to compare the current parameters to the previous parameters and decide if the code needs to be executed again? What is "best practice" in this case?
My understanding is that usually all variables inside he method are reset to zero and nil when the method is called.
Edit
By request I have added a code sample:
- (NSArray*)periodsForCompanies:(NSArray*)companies figureType:(NSString*)figureType actualValuesOnly:(BOOL)actualValuesOnly
{
// fetch all periods
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"IBEstPeriod"];
NSPredicate *predicate = [[NSPredicate alloc]init];
if (actualValuesOnly == YES)
predicate = [NSPredicate predicateWithFormat:@"(company IN %@) AND (ANY estType.actValue != nil) AND (ANY estType.type == %@)", self.companies, figureType];
else
predicate = [NSPredicate predicateWithFormat:@"(company IN %@) AND (ANY estType.type == %@)", self.companies, figureType];
request.predicate = predicate;
request.resultType = NSDictionaryResultType;
request.returnsDistinctResults = YES;
request.propertiesToFetch = @[@"endCalYear",@"endMonth",@"periodLength"];
request.sortDescriptors =
@[[NSSortDescriptor sortDescriptorWithKey:@"endCalYear" ascending:NO],
[NSSortDescriptor sortDescriptorWithKey:@"endMonth" ascending:NO],
[NSSortDescriptor sortDescriptorWithKey:@"periodLength" ascending:NO]];
NSError *fetchError = nil;
NSArray *results = [self.managedObjectContext executeFetchRequest:request error:&fetchError];
NSMutableArray *distinctPeriods = results.mutableCopy;
if (fetchError) {
NSLog(@"Error during fetch request:%@", [fetchError localizedDescription]);
} else {
// NSLog(@"results: %@",results);
}
// remove periods for which not all companies have data for estimate type specified
NSString const *endCalYearKey = @"endCalYear";
NSString const *endMonthKey = @"endMonth";
NSString const *periodLengthKey = @"periodLength";
const NSIndexPath *yoyGrowthIndexPath = [NSIndexPath indexPathForRow:0 inSection:0];
const NSIndexPath *seqGrowthIndexPath = [NSIndexPath indexPathForRow:1 inSection:0];
const NSIndexPath *customGrowthIndexPath = [NSIndexPath indexPathForRow:2 inSection:0];
NSMutableIndexSet *indexesForPeriodsToRemove = [NSMutableIndexSet indexSet];
[distinctPeriods enumerateObjectsUsingBlock:^(NSDictionary *estPeriodDict, NSUInteger idx, BOOL *stop) {
NSNumber *endCalYear = estPeriodDict[endCalYearKey];
NSNumber *endMonth = estPeriodDict[endMonthKey];
NSNumber *periodLength = estPeriodDict[periodLengthKey];
NSPredicate *predicate = [[NSPredicate alloc]init];
UITableView *tableView = [self tableView];
if ( [[tableView indexPathForSelectedRow] isEqual:customGrowthIndexPath] ) {
// company predicate:
predicate = [NSPredicate predicateWithFormat: @"ANY estimateType.type == %@ "
"AND SUBQUERY(estimatePeriod, $x, $x.endCalYear == %@ AND $x.endMonth == %@ AND $x.periodLength == %@ AND ANY $x.estType.type == %@).@count > 0",
figureType,
endCalYear, endMonth, periodLength, figureType];
} else if ( [[tableView indexPathForSelectedRow] isEqual:yoyGrowthIndexPath] ) {
predicate = [NSPredicate predicateWithFormat: @"ANY estimateType.type == %@ "
"AND SUBQUERY(estimatePeriod, $x, $x.endCalYear == %@ AND $x.endMonth == %@ AND $x.periodLength == %@ AND ANY $x.estType.type == %@).@count > 0"
"AND SUBQUERY(estimatePeriod, $x, $x.endCalYear == %i AND $x.endMonth == %@ AND $x.periodLength == %@ AND ANY $x.estType.type == %@).@count > 0",
figureType,
endCalYear, endMonth, periodLength, figureType,
endCalYear.integerValue - 1, endMonth, periodLength, figureType];
} else if ( [[tableView indexPathForSelectedRow] isEqual:seqGrowthIndexPath] ) {
// TODO: rewrite
predicate = [NSPredicate predicateWithFormat: @"ANY estimateType.type == %@ "
"AND SUBQUERY(estimatePeriod, $x, $x.endCalYear == %@ AND $x.endMonth == %@ AND $x.periodLength == %@ AND ANY $x.estType.type == %@).@count > 0",
figureType,
endCalYear, endMonth, periodLength, figureType];
} else {
[NSException raise:NSInternalInconsistencyException format:@"TableView: Invalid selection state in section 0 (NSIndexPath: %@)",super.tableView.indexPathForSelectedRow];
}
NSArray *companiesWithDataForPeriod = [self.companies filteredArrayUsingPredicate:predicate];
NSLog(@"type: %@, period: %@/%@(%@), companies: %@", figureType, endCalYear, endMonth, periodLength,[companiesWithDataForPeriod valueForKey:@"contrSymbol"]);
// mark periods which are not defined for all companies for removal (from display):
if ( companiesWithDataForPeriod.count < self.companies.count ) [indexesForPeriodsToRemove addIndex:idx];
}]; // end block
[distinctPeriods removeObjectsAtIndexes:indexesForPeriodsToRemove];
return distinctPeriods;
}
You need to make some kind of caching, actually your parameters can vary very much because you have companies array as an argument. That might cause a lot of memory will be used if you call this method using a lot of combinations.
I would change it to pass only single company so the cache would be smaller instead of keeping all combinations of companies in the worst case.
To keep data in cache you can make some
NSMutableDictionaryproperty or static value (remember to release/empty it somewhere), then as a key you would keep these arguments combined in some object or as a simple string.So your internal implementation of your method would look like: