NSSortDescriptor
Sorting: it’s the mainstay of Computer Science 101 exams and whiteboarding interview questions. But when was the last time you actually needed to know how to implement Quicksort yourself?
When making apps, sorting is just something you can assume to be fast, and utility is a function of convenience and clarity of intention. And when it comes to that, you’d be hard-pressed to find a better implementation than Foundation’s NSSort
.
NSSort
objects are constructed with the following parameters:
key
: for a given collection, the key for the corresponding value to be sorted on for each object in the collection.ascending
: a boolean specifying whether the collection should be sorted in ascending (YES
) or descending (NO
) order.
There is an optional third parameter that relates to how the sorted values are compared to one another. By default, this is a simple equality check, but this behavior can be changed by passing either a selector
(SEL
) or comparator
(NSComparator
).
Any time you’re sorting user-facing strings, be sure to pass the selector
localized
, which will sort according to the language rules of the current locale (locales may differ on ordering of case, diacritics, and so forth).Standard Compare:
Collection classes like NSArray
and NSSet
have methods to return sorted arrays of the objects that take an array of sort
. Sort descriptors are applied in order, so that if two elements happen to be tied for a particular sorting criteria, the tie is broken by any subsequent descriptors.
To put that into more practical terms, consider a Person
class with properties for first
& last
of type NSString *
, and age
, which is an NSUInteger
.
class Person: NSObject {
let first Name: String
let last Name: String
let age: Int
init(first Name: String, last Name: String, age: Int) {
self.first Name = first Name
self.last Name = last Name
self.age = age
}
override var description: String {
return "\(first Name) \(last Name)"
}
}
@interface Person : NSObject
@property NSString *first Name;
@property NSString *last Name;
@property NSNumber *age;
@end
@implementation Person
- (NSString *)description {
return [NSString string With Format:@"%@ %@", self.first Name, self.last Name];
}
@end
Given the following dataset:
first |
last |
age |
---|---|---|
Alice | Smith | 24 |
Bob | Jones | 27 |
Charlie | Smith | 33 |
Quentin | Alberts | 31 |
Here are some of the different ways they can be sorted by combinations of NSSort
:
let alice = Person(first Name: "Alice", last Name: "Smith", age: 24)
let bob = Person(first Name: "Bob", last Name: "Jones", age: 27)
let charlie = Person(first Name: "Charlie", last Name: "Smith", age: 33)
let quentin = Person(first Name: "Quentin", last Name: "Alberts", age: 31)
let people = [alice, bob, charlie, quentin]
let first Name Sort Descriptor = NSSort Descriptor(key: "first Name", ascending: true, selector: "localized Standard Compare:")
let last Name Sort Descriptor = NSSort Descriptor(key: "last Name", ascending: true, selector: "localized Standard Compare:")
let age Sort Descriptor = NSSort Descriptor(key: "age", ascending: false)
let sorted By Age = (people as NSArray).sorted Array Using Descriptors([age Sort Descriptor])
// "Charlie Smith", "Quentin Alberts", "Bob Jones", "Alice Smith"
let sorted By First Name = (people as NSArray).sorted Array Using Descriptors([first Name Sort Descriptor])
// "Alice Smith", "Bob Jones", "Charlie Smith", "Quentin Alberts"
let sorted By Last Name First Name = (people as NSArray).sorted Array Using Descriptors([last Name Sort Descriptor, first Name Sort Descriptor])
// "Quentin Alberts", "Bob Jones", "Alice Smith", "Charlie Smith"
NSArray *first Names = @[ @"Alice", @"Bob", @"Charlie", @"Quentin" ];
NSArray *last Names = @[ @"Smith", @"Jones", @"Smith", @"Alberts" ];
NSArray *ages = @[ @24, @27, @33, @31 ];
NSMutable Array *people = [NSMutable Array array];
[first Names enumerate Objects Using Block:^(id obj, NSUInteger idx, BOOL *stop) {
Person *person = [[Person alloc] init];
person.first Name = [first Names object At Index:idx];
person.last Name = [last Names object At Index:idx];
person.age = [ages object At Index:idx];
[people add Object:person];
}];
NSSort Descriptor *first Name Sort Descriptor = [NSSort Descriptor sort Descriptor With Key:@"first Name"
ascending:YES
selector:@selector(localized Standard Compare:)];
NSSort Descriptor *last Name Sort Descriptor = [NSSort Descriptor sort Descriptor With Key:@"last Name"
ascending:YES
selector:@selector(localized Standard Compare:)];
NSSort Descriptor *age Sort Descriptor = [NSSort Descriptor sort Descriptor With Key:@"age"
ascending:NO];
NSLog(@"By age: %@", [people sorted Array Using Descriptors:@[age Sort Descriptor]]);
// "Charlie Smith", "Quentin Alberts", "Bob Jones", "Alice Smith"
NSLog(@"By first name: %@", [people sorted Array Using Descriptors:@[first Name Sort Descriptor]]);
// "Alice Smith", "Bob Jones", "Charlie Smith", "Quentin Alberts"
NSLog(@"By last name, first name: %@", [people sorted Array Using Descriptors:@[last Name Sort Descriptor, first Name Sort Descriptor]]);
// "Quentin Alberts", "Bob Jones", "Alice Smith", "Charlie Smith"
NSSort
can be found throughout Foundation and other system frameworks, playing an especially prominent role in Core Data. Anytime your own classes need to define sort ordering, follow the convention of specifying a sort
parameter as appropriate.
Because, in reality, sorting should be thought of in terms of business logic, not mathematical formulas and map-reduce functions. In this respect, NSSort
is a slam dunk, and will have you pining for it anytime you venture out of Objective-C and Cocoa.