Sorting with sort descriptors

September 20th, 2011
#sorting

Sorting an array of NSNumber elements in Objective-C is fairly straight forward:

NSArray *unsortedArray = @[@(9), @(5), @(1), @(2), @(4)];
NSArray *sortedArray = [unsortedArray sortedArrayUsingSelector:@selector(compare:)];

NSLog(@"sortedArray: %@", sortedArray);

But what happens if we have something more complex, where the value being compared isn't the type itself but a property of that type?

The simple approach above won't work. Instead, we need to use a NSSortDescriptor. NSSortDescriptor allows us to sort the elements in an array based on that element's properties rather than the element itself.

Photo of tea bags that have been sorted

Take the following class:

@interface WBWReview : NSObject

@property (nonatomic, assign) NSUInteger ratings;
@property (nonatomic, assign) NSUInteger votes;

@end

@implementation WBWReview

#pragma mark - Init

- (instancetype)initWithRatings:(NSUInteger)ratings votes:(NSUInteger)votes {
    self = [super init];
    
    if (self) {
        _ratings = ratings;
        _votes = votes;
    }
    
    return self;
}

@end

If we wanted to sort an array of Review instances by ratings then we would configure a NSSortDescriptor instance like:

NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"ratings" ascending:NO];

NSArray *sortedArray = [unsortedArray sortedArrayUsingDescriptors:@[sortDescriptor]]; 

NSLog(@"sortedArray: %@", sortedArray);

With the above code snippet, we configure an NSSortDescriptor instance to sort the unsortedArray using the ratings property of each element in that array, in descending order.

If the elements of the array don't have a ratings property, then the above code would crash.

And if we wanted to sort by votes:

NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"votes" ascending:NO];

NSArray *sortedArray = [unsortedArray sortedArrayUsingDescriptors:@[sortDescriptor]]; 

NSLog(@"sortedArray: %@", sortedArray);

Almost the same, the only thing that changed between the two examples is that the key of the NSSortDescriptor instance: from ratings to votes.

While sorting by one property is super, it's possible to combine NSSortDescriptor instances and sort by more than one property.

If instead we wanted to sort by votes and then ratings then we would flip the order of the NSSortDescriptor instances when applied to the array:

NSSortDescriptor *ratingsSortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"ratings" ascending:NO];
NSSortDescriptor *votesSortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"votes" ascending:NO];

NSArray *sortedArray = [unsortedArray sortedArrayUsingDescriptors:@[ratingsSortDescriptor, votesSortDescriptor]];

NSLog(@"sortedArray: %@", sortedArray);

The above code snippet would sort unsortedArray first by ratings and then if two (or more) Review instances had the same ratings value by votes.

Straightforward stuff but super-powerful.

What do you think? Let me know by getting in touch on Twitter - @wibosco

William Boles

William has been an iOS developer since 2010, after all these years he just hopes that the String API will stop changing now.