Shuffling with Objective-C Categories


10.21.09 Posted in iphone by

One of the neat features of Objective-C programming is categories. Categories give the ability to add discrete bits of functionality on existing classes. Think of them as lightweight inheritance; I can add one or more methods to an existing class without having to create a custom subclass. There’s a bunch more you can do with categories as described in the Mac Dev Center.

In a current project I’m working on, I’m dealing with many arrays whose objects need to be in a random order. There are many ways of accomplishing the randomization of array order, but having implemented the Fisher-Yates shuffling algorithm in other languages and projects, I decided to implement an Objective-C version of it. It’s a deceivingly simple algorithm, and there’s a great post on why the Fisher-Yates method beats the pants off a naive shuffling algorithm over at Coding Horror.

I decided the easiest implementation would be add the category directly into NSArray and NSMutableArray. This way all the shuffling logic is fully encapsulated within those classes. Another article got me started on how to do this with the shuffling algorithm I wanted to use. This got me 90% the way there.

Basically all you do is create a new header and implementation file. For the interface, you define the new category and methods for both NSArray and NSMutableArray:


@interface NSArray (Shuffle)
- (NSArray*) shuffledArray;
@end

@interface NSMutableArray (Shuffle)
- (void) shuffle;
@end

And the implementation:


@implementation NSMutableArray (Shuffle)
- (void) shuffle {
  for (NSInteger i = [self count] - 1; i > 0; --i) {
    [self exchangeObjectAtIndex: arc4random() % (i+1) withObjectAtIndex: i];
  }
}
@end

@implementation NSArray (Shuffle)
- (NSArray*) shuffledArray {
  NSMutableArray* shuffledArray = [NSMutableArray arrayWithArray: self];
  [shuffledArray shuffle];
  return shuffledArray;
}
@end

Note I’m using arc4random() rather than random(). There are a few benefits to using arc4random(), but I like the fact that this randomizer seeds itself.

So that’s pretty much it. Once you include your new header wherever you want to use the shuffle category methods, you can shuffle any NSArray or NSMutableArray you come across.

Imagine a simple image slide-show application:


// Collection of UIImage *s
NSMutableArray *imageArray = [self makeImageArray];

// Shuffle array
[imageArray shuffle];

// Now we have a shuffled array. We can access items from the front.
UIImage *nextImageToDisplay = [imageArray objectAtIndex:0];

// If we don't need to keep track of previously shown images
[imageArray removeObjectAtIndex:0];

Or automatically shuffling a collection of songs a user has picked using the MPMediaPickerController (you can also shuffle songs using the shuffleMode property of the MPMusicPlayerController, but they don’t get shuffled until they are played):

// MPMediaPickerController delegate
- (void) mediaPicker: (MPMediaPickerController *) mediaPicker didPickMediaItems: (MPMediaItemCollection *) mediaItemCollection {

  [self dismissModalViewControllerAnimated: YES];

  // mediaItemCollection.items is an NSArray,
  // so we create a new MPMediaItemCollection using -shuffledArray.
  MPMediaItemCollection *shuffledCollection = [MPMediaItemCollection collectionWithItems:[mediaItemCollection.items shuffledArray]];

  // Now we've got a new collection of shuffled items.
  [self playMediaCollection:shuffledCollection];
}

I’ve glossed over many details here here in an attempt to show a practical use for categories and shuffling. If you’re interested in learning more, I’d recommend checking out the links in this post. They are excellent references.



Leave a Reply