McsIQ

Sorting objects in Cocoa

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&nbsp;*bag&nbsp;=&nbsp;[[[NSMutableSet&nbsp;alloc]&nbsp;init]&nbsp;autorelease]; [bag addObject:[[Place&nbsp;alloc]&nbsp;initWithCountry:@"USA"&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; city:@"Springfield"&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; imageUrl:[NSURL&nbsp;URLWithString:@"<a href="http://www.agbo.biz/">http://www.agbo.biz</a>"]]]; [bag addObject:[[Place&nbsp;alloc]&nbsp;initWithCountry:@"Afghanistan"&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; city:@"Tora Bora"&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; imageUrl:[NSURL&nbsp;URLWithString:@"<a href="http://www.agbo.biz/">http://www.agbo.biz</a>"]]]; [bag addObject:[[Place&nbsp;alloc]&nbsp;initWithCountry:@"USA"&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; city:@"Chicago"&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; imageUrl:[NSURL&nbsp;URLWithString:@"<a href="http://www.agbo.biz/">http://www.agbo.biz</a>"]]]; [bag addObject:[[Place&nbsp;alloc]&nbsp;initWithCountry:@"USA"&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; city:@"Chicago"&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; imageUrl:[NSURL&nbsp;URLWithString:@"<a href="http://www.google.com/">http://www.google.com</a>"]]]; 
NSSortDescriptor&nbsp;*country&nbsp;=&nbsp;[[[NSSortDescriptor&nbsp;alloc]&nbsp;initWithKey:@"country"&nbsp;ascending:YES]autorelease]; NSSortDescriptor&nbsp;*city&nbsp;=&nbsp;[[[NSSortDescriptor&nbsp;alloc]&nbsp;initWithKey:@"city"&nbsp;ascending:YES]autorelease]; NSArray&nbsp;*sorted&nbsp;=&nbsp;[bag sortedArrayUsingDescriptors:[NSArray&nbsp;arrayWithObjects:&nbsp;country,&nbsp;city,&nbsp;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

Acerca de Fernando Rodriguez

Fundador & Editor Jefe de justcodeit, Fernando Rodríguez (@frr149 & Linkedin) es desarrollador & un experto en la enseñanza de máxima calidad en programación y desarrollo para dispositivos iOS, Cocoa Touch, Objective C, Swift, Python, entre otros, aunque su mejor carta de presentación, es la opinión de sus alumnos: http://keepcoding.io/es/testimonio/ CLO en KeepCoding & Arunovo. Instructor de iOS Avanzado del Big Nerd Ranch. Profesor Asociado de la U-tad, autor invitado de revistas como iPhoneWorld, Applesfera.com & ponente habitual en conferencias dentro y fuera de España (iOSDevUK, CodeMotion, BCNDevCon, etc). En sus vidas anteriores fue un nerd de Python y Django, mago de Smalltalk, y para su pesar, galeote de C++ y un gran cocinero.

Share this:

Leave a comment