|
Mar
22
|
Last night, I was working through an iPhone tutorial which involved setting up a Table View based on an array of strings, and then providing the ability to add in extra strings (and have the Table View update itself accordingly). Being somewhat anally retentive as I am, I decided to extend the tutorial slightly so that it would alphabetically order the array before displaying it. So I jumped into the API, and here’re some notes on what I found, which is intended to be reasonably comprehensive. This is intended to be fairly low level (just like me! :P), and will probably repeat a lot of information that can be found in the APIs with some extra notes and comments.
Firstly, one key, if hopefully obvious, point: ordering an array works differently, depending on whether you’re working with an NSArray or an NSMutableArray. This post will deal primarily with the mutable kind, although the non-mutable kind is not too dissimilar, and I’ll touch on that later.
NSMutableArray has supports ordering through three different mechanisms: sortUsingDescriptors:, sortUsingFunction:context:, and sortUsingSelector:. Let’s start with the first.
Sorting with - (void)sortUsingDescriptors:(NSArray *)sortDescriptors
Here is a quick function to show how it can be done (this was written for an array of NSStrings), with an explanation following:
- (void)addAndSortDescSortObject:(id)object toArray:(NSMutableArray *)array {
// Add object to array
[array addObject:object];
// Sort array using an NSSortDescriptor
NSSortDescriptor * sortDesc = [[NSSortDescriptor alloc] initWithKey:@”self” ascending:YES];
[array sortUsingDescriptors:[NSArray arrayWithObject:sortDesc]];
[sortDesc release];
} // andAndSortDescSortObject:toArray:
(Note: The sort code could be reduced to a single line by making sortDesc autorelease, but in the interests of readability I’ve left it split up.)
Let’s look at the 2nd bold line first, at the actual sortUsingDescriptors: message. This function expects an array of NSSortDescriptors (more on these in 2 secs!), but we’re only using the one, so the turn it into a single object array using NSArray’s arrayWithObject: method.
The interesting part of this happens in the first bold line though, with the creation of the NSSortDescriptor, which allows up to set a number of details regarding the sort behaviour. According to the Class Reference, an NSSortDescriptor is:
An instance of
NSSortDescriptordescribes a basis for ordering objects by specifying the property to use to compare the objects, the method to use to compare the properties, and whether the comparison should be ascending or descending. Instances ofNSSortDescriptorare immutable.
We have two options for initialising the NSSortDescriptor: initWithKey:ascending: and initWithKey:ascending:selector:. Let’s break these down:
initWithKey:(NSString *)keyPath– the property that is used when making the comparisons. In other words, when two objects are compared, what actually gets compared are the values of[comparedObject keyPath]– this is why in my above example, which was written forNSStrings, the supplied key is@"self". For more complex objects, a more specific key is probably beneficial.ascending:(BOOL)ascending– passingYEShere will specify that objects should be sorted in ascending order,NOspecifies descending order.selector:(SEL)selector– the method to use to determine order. It’s method signature must be of the form:- (NSComparisonResult)customCompare:(NSString *)aString. In the case of the 2 keyword init method, the default comparison method (compare:) is used.
(As a side note, an init‘ed NSSortDescriptor can also be used for manually ordering objects, through calling the method: - (NSComparisonResult)compareObject:(id)object1 toObject:(id)object2.)
Naturally, the 3rd bold line is just us being good little boys and girls and releasing our NSSortDescriptor. Only you can fight memory leaks!
Sorting with - (void)sortUsingFunction:(NSInteger (*)(id, id, void *))compare context:(void *)context
This method allows us to sort an array using a custom C function, as well as our own custom context. Break it down!:
sortUsingFunction:(NSInteger (*)(id, id, void *))compare– a three-argument function which does most of the work here (note the C-style function signature). The function compares two objects, and returns the relevantNSComparisonresult. Ifobj1 < obj2,NSOrderedAscendingshould be returned; ifobj1 == obj2,NSOrderedSameshould be returned; and ifobj1 > obj2,NSOrderedDescendingshould be returned. The third argument is the context passed into the function, which we will look at presently.context:(void *)context– a context argument, which gets passed to the comparison function. This allows external parameters (for example whether the sort is case-sensitive or -insensitive) to determine the comparison, without needing to change the comparison function.
Here’s some example code using this approach, although it doesn’t make use of the context. Just for something different, this sorts an array of strings by their length:
// First, our comparison function
int lengthSort( id obj1, id obj2, void *context ) {
// Get string lengths
int int1 = [obj1 length];
int int2 = [obj2 length];
// Compare and return
if( int1 < int2 )
return NSOrderedAscending;
else if( int1 == int2 )
return NSOrderedSame;
else
return NSOrderedDescending;
} // lengthSort( id, id, void * )
- (void)addAndFunctionSortObject:(id)object toArray:(NSMutableArray *)array {
// Add object to array
[array addObject:object];
// Sort array using our lengthSort function
[array sortUsingFunction:lengthSort context:nil];
} // addAndFunctionSortObject:toArray:
Sorting with - (void)sortUsingSelector:(SEL)comparator
This is the third option, which lets us sort using an Obj-C method to sort our array. The main difference between this method and the previous one is that, instead of creating a new function to compare the objects, this uses a selector belonging to the objects being compared. In other words, the following message is sent [object1 comparator:object2], which shouls again return NSOrderedAscending if the receiver (object1) is smaller than the argument, NSOrderedSame if they’re the same, and NSOrderedDescending is the receiver is the larger of the two.
Here’s an example, for ordering an array of NSStrings. In this case, we’re gonna use NSString’s localizedCaseInsensitiveCompare: method (why? because we can!):
- (void)addAndSelectorSortObject:(id)object toArray:(NSMutableArray *)array {
// Add object to array
[array addObject:object];
// Sort array using NSString’s localizedCaseInsensitiveCompare:
[array sortUsingSelector:@selector(localizedCaseInsensitiveCompare:)];
} // addAndSelectorSortObject:toArray:
Easy! But also useful when making your own classes and you know you’ll want to sort them at some stage, especially if you have some more complicated sort logic.
Sorting NSArrays
Given NSArray’s immutable nature, we can’t directly sort the elements within the array. What the class does give us, however, is the following set of methods:
sortedArrayUsingDescriptors:sortedArrayUsingFunction:context:sortedArrayUsingFunction:context:hint:sortedArrayUsingSelector:
Hopefully you’ll notice something familiar about these methods. They do the same kind of sorts as the NSMutableArray functions, but instead of modifying the original array, they return a second array, whose objects are properly sorted. Given this second array retains all of the objects in the original array, we need to be careful with releasing both arrays when they’re no longer needed. Also, given NSMutableArray is a subclass of NSArray, all of these methods are also available to both classes (although it will always return a static NSArray).
You will notice one new function in here: sortedArrayUsingFunction:context:hint:. This apparently works in a similar way to sortedArrayUsingFunction:context:, except it uses a hint (generated by NSArray’s sortedArrayHint method) to speed up the sorting process, which may be useful for sorting large arrays where only small changes have been made. Honestly I’m not entirely sure about the full advantages of using this.
Anyways, that’s it! This ended up being a lot longer than originally intended
Still, I learnt something writing it, and if anyone else does too, even better. Finally, any and all comments, corrections or whatever are welcome!
October 11th, 2009 at 4:13 am
From the second to last paragraph, “Given this second array retains all of the objects in the original array, we need to be careful with releasing both arrays when they’re no longer needed.”
Actually I believe that you the arrays are “autoreleased” and you really need to be careful to retain them if you want to keep the data.
October 12th, 2009 at 4:21 pm
You’re absolutely right on that one. It’s such a silly mistake, and one I like to think I wouldn’t make now. It is interesting to now look back on how much more familiar I am with Apple’s naming conventions and stuff, compared to when I wrote the post (I think at that time I was still trying to wrap my head around the idea of an auto-released object
December 3rd, 2009 at 4:18 pm
Thanks a lot!!!
You helped finally think i got it.
Regarding the “hint” parameter, its probably for passing the array median value in some implementations of “quick sort” method.
February 25th, 2010 at 1:36 pm
Thank you, very helpful in clarifying Apple’s own docs.