December 13, 2014

Swift: Death to Telescoping Constructors

One of the many things about Swift that has made me happy is the introduction of default parameter values in methods. In Objective-C, it’s pretty common to see APIs that look a lot like the following code (taken from MagicalRecord):

// NSManagedObjectContext+MagicalFinders.h

+ (instancetype)findFirstInContext:(NSManagedObjectContext *)context;

+ (instancetype)findFirstWithPredicate:(NSPredicate *)searchTerm
                             inContext:(NSManagedObjectContext *)context;

+ (instancetype)findFirstWithPredicate:(NSPredicate *)searchterm
                              sortedBy:(NSString *)property
                             ascending:(BOOL)ascending
                             inContext:(NSManagedObjectContext *)context;


// NSManagedObjectContext+MagicalFinders.m

+ (instancetype)findFirstInContext:(NSManagedObjectContext *)context
{
  return [self findFirstWithPredicate:nil
                             sortedBy:nil
                            inContext:context];
}

+ (instancetype)findFirstWithPredicate:(NSPredicate *)searchTerm
                             inContext:(NSManagedObjectContext *)context
{
  return [self findFirstWithPredicate:searchTerm
                             sortedBy:nil
                            ascending:YES
                            inContext:context];
}2
+ (instancetype)findFirstWithPredicate:(NSPredicate *)searchterm
                              sortedBy:(NSString *)sortedBy
                             ascending:(BOOL)ascending
                             inContext:(NSManagedObjectContext *)context
{
  // Actual implementation of method
}

This pattern is often referred to as the “telescopic pattern”, or “telescoping methods”. Basically, you implement a number of methods that each call the next, more specific method in the chain, providing default values at each stage as they become necessary. Users of the API can choose to use any of the methods, allowing for more succinct code, but only requiring a single “real” method implementation.

In Swift, this functionality is built into the language (hooray!). As an example, given the following method in Swift:

func findFirstWithPredicate(predicate:NSPredicate? = nil,
                            sortedBy:String? = nil,
                            ascending:Bool = true,
                            inContext context:NSManagedObjectContext) -> [NSManagedObject] {
  // Actual implementation of method
}

Users of this Swift API can choose not to include any parameters that have default values, ie:

let results = findFirstWithPredicate(inContext:myContext);

// OR

let otherResults = findFirstWithPredicate(sortedBy:"someProperty",
                                          inContext:myContext);

// OR

let moreResults = findFirstWithPredicate(sortedBy:"someProperty",
                                         ascending:false,
                                         inContext:myContext);

// You get the gist of it!

This allows for more succinct methods calls, without the mountain of boilerplate we would have had to write in Objective-C!