Sorting objects in Cocoa

Autor: | Última modificación: 13 de octubre de 2022 | Tiempo de Lectura: 5 minutos
Temas en este post:

Algunos de nuestros reconocimientos:

Premios KeepCoding

Sorting objects in Cocoa: Sorting using selector (in ascending order)

This is the most common sorting method for sorting objects in Cocoa. It uses a selector that must return an NSComparisonResult (either NSOrderedAscending, NSOrderedSame, or NSOrderedDescending).
The most common selector is compare:.  MUST be declared in header file, as NSObject doesn’t implement it. All custom classes should implement compare:
– (void)sortUsingSelector:(SEL)comparator 
– (NSArray *)sortedArrayUsingSelector:(SEL)comparator
If your NSMutableArray is full of NSStrings or NSNumbers, you can just do:
[myArray sortUsingSelector:@selector(compare:)];
and it will sort them appropriately because NSString and NSNumber both properly implement compare:. The idea is that every class should implement a compare: method that makes the receiver compare itself and return the right result.
sortUsingSelector: gets more interesting when you have objects that aren’t simply strings or numbers. For instance, let’s say I have a class Person with instance variables (and corresponding accessor methods) firstName and lastName whose objects I want to be able to sort by name (in the form «Last, First»). I can implement something like this:
– (NSComparisonResult)comparePerson:(Person *)p
{
    return [[NSString stringWithFormat:@»%@, %@»,
            [self lastName], [self firstName]]
            compare:
            [NSString stringWithFormat:@»%@, %@»,
                                       [p lastName],
                                       [p firstName]];
}
Using this method with sortUsingSelector: will sort all Nelsons before all Smiths, and Abby Smith before Bernard Smith.
Of course, you can make things more flexible by deferring the sort order until runtime. Sometimes you may want to sort by first name instead of last name. In this case, the best thing to do is probably something like this:
– (NSComparisonResult)comparePerson:(Person *)p
{
    return [[self stringForSorting] compare:[p stringForSorting]];
}

– (NSString *)stringForSorting
{
    if (something)  // determine sorting type here
        return [NSString stringWithFormat:@»%@, %@»,
                                          [self lastName],                                               [self firstName]];
    // else…
        return [NSString stringWithFormat:@»%@ %@»,
                                          [self firstName],                                             [self lastName]];
}
You would replace the «if (something)» condition with something useful; maybe check a user preference, or the state of something in the GUI.

Sorting using Descriptors

This is a more complex and flexible way of sorting. Can sort in ascending and descending order and using several attributes(ie, sort first by attribute name in ascending order, then by attribute  age in descending order).
The most common methods are:
– (void)sortUsingDescriptors:(NSArray *)sortDescriptors
– (NSArray *)sortedArrayUsingDescriptors:(NSArray *)sortDescriptors
Both take an array of NSSortDescriptorObjects
NSSortDescriptor
+ sortDescriptorWithKey:ascending:
– initWithKey:ascending:
+ sortDescriptorWithKey:ascending:selector:
– initWithKey:ascending:selector:
+ sortDescriptorWithKey:ascending:comparator:
– initWithKey:ascending:comparator:
Example:
NSMutableSet *bag = [[[NSMutableSet alloc] init] autorelease]; [bag addObject:[[Place alloc] initWithCountry:@"USA"                                           city:@"Springfield"                                       imageUrl:[NSURL URLWithString:@"http://www.agbo.biz"]]]; [bag addObject:[[Place alloc] initWithCountry:@"Afghanistan"                                           city:@"Tora Bora"                                       imageUrl:[NSURL URLWithString:@"http://www.agbo.biz"]]]; [bag addObject:[[Place alloc] initWithCountry:@"USA"                                           city:@"Chicago"                                       imageUrl:[NSURL URLWithString:@"http://www.agbo.biz"]]]; [bag addObject:[[Place alloc] initWithCountry:@"USA"                                           city:@"Chicago"                                       imageUrl:[NSURL URLWithString:@"http://www.google.com"]]]; 
NSSortDescriptor *country = [[[NSSortDescriptor alloc] initWithKey:@"country" ascending:YES]autorelease]; NSSortDescriptor *city = [[[NSSortDescriptor alloc] initWithKey:@"city" ascending:YES]autorelease]; NSArray *sorted = [bag sortedArrayUsingDescriptors:[NSArray arrayWithObjects: country, city, nil]];

Sorting using Blocks

You provide an NSComparator block that takes the 2 objects and returns an NSComparisonResult.
The relevant methods are:
– (void)sortUsingComparator:(NSComparator)cmptr
– (NSArray *)sortedArrayUsingComparator:(NSComparator)cmptr
Example (from 10 uses of blocks):
float target = 5.0f;
[someArray sortedArrayUsingComparator:^(id obj1, id obj2) {
    float diff1 = fabs([obj1 floatValue] – targetValue);
    float diff2 = fabs([obj2 floatValue] – targetValue)
    if ( diff1 < diff2 )
        return NSOrderedAscending;
    else if ( diff1 > diff2 )
        return NSOrderedDescending;
    else
        return NSOrderedSame;
}];

Which one should you use?

For default sorting, implement compare: and use sortUsingSelector: @selector(compare:).
For quickies, use blocks or NSSortDescriptors