pvanallen
pvanallen

Reputation: 549

Faster sort of NSMutableArray of MPMediaItems? Code Review

I'm a couple weeks into iOS programming, and have lots to learn. I've got a sort of an NSMutableArray containing MPMediaItems working, but it's about 10 seconds slow with a sort of 1200 items and I'm looking for an approach that would be faster.

My ultimate goal is to have an array of MPMediaItemCollection items, each representing an album. I can't get this from an MPMediaQuery (as far as I know) because I need to get the songs from a playlist. So I'm sorting the songs I get from a specific playlist ("Last 4 months") and will then build my own array of collections. As I say, the approach below works but is very slow. Even if I sort only by the MPMediaItemPropertyAlbumTitle, it still takes about 4 seconds (iPhone 4S).

EDIT: I should mention that I tried sort descriptors, but I can't get the key to work. E.g.

NSSortDescriptor *titleDescriptor = [[NSSortDescriptor alloc] initWithKey:@"MPMediaItemPropertyAlbumTitle" ascending:YES];

This return an error of

[<MPConcreteMediaItem 0x155e50> valueForUndefinedKey:]: this class is not key value coding-compliant for the key MPMediaItemPropertyAlbumTitle.

The Code

MPMediaQuery *query = [MPMediaQuery playlistsQuery];
NSArray *playlists = [query collections];
NSMutableArray *songArray = [[NSMutableArray alloc] init];

for (MPMediaItemCollection *playlist in playlists) {
    NSString *playlistName = [playlist valueForProperty: MPMediaPlaylistPropertyName];
    NSLog (@"%@", playlistName);
    if ([playlistName isEqualToString:@"Last 4 months"]) {

        /* replaced this code with a mutable copy
        NSArray *songs = [playlist items];
        for (MPMediaItem *song in songs) {
            [songArray addObject:song]; 
        }
        */
        // the following replaces the above for-loop
        songArray = [[playlist items] mutableCopy ];

        [songArray sortUsingComparator:^NSComparisonResult(id a, id b) {
            NSString *first1 = [(MPMediaItem*)a valueForProperty:MPMediaItemPropertyAlbumTitle];
            NSString *second1 = [(MPMediaItem*)b valueForProperty:MPMediaItemPropertyAlbumTitle];

            NSString *first2 = [(MPMediaItem*)a valueForProperty:MPMediaItemPropertyAlbumPersistentID];
            NSString *second2 = [(MPMediaItem*)b valueForProperty:MPMediaItemPropertyAlbumPersistentID];

            NSString *first3 = [(MPMediaItem*)a valueForProperty:MPMediaItemPropertyAlbumTrackNumber];
            NSString *second3 = [(MPMediaItem*)b valueForProperty:MPMediaItemPropertyAlbumTrackNumber];

            NSString *first = [NSString stringWithFormat:@"%@%@%03d",first1,first2, [first3 intValue]];
            NSString *second = [NSString stringWithFormat:@"%@%@%03d",second1,second2, [second3 intValue]];

            return [first compare:second]; 
        }];
    }
}

Upvotes: 2

Views: 2144

Answers (3)

Anthony Myatt
Anthony Myatt

Reputation: 346

I know this is an old thread but I thought it important to clarify why the OPs sort code didn't work so that anyone who comes across this code isn't put off of using NSSortDescriptors.

The code:

[[NSSortDescriptor alloc] initWithKey:@"MPMediaItemPropertyAlbumTitle" ascending:YES];

Should be:

[[NSSortDescriptor alloc] initWithKey:MPMediaItemPropertyAlbumTitle ascending:YES];

as MPMediaItemPropertyAlbumTitle holds the name of the key.

I have used this code many times.

Upvotes: 3

pvanallen
pvanallen

Reputation: 549

With the suggestion of @cdelacroix I reimplemented my comparison block to cascade the three sort keys, only checking lower order keys if the higher order keys were the same. This resulted in a more than 50% reduction in execution time of the sort. Still, it's not as fast as I would like, so if anyone has a better answer, please post it.

Here are old times vs. new times (1221 items):

4S executionTime = 10.8, executionTime = 4.7

3GS executionTime = 21.6, executionTime = 9.3

Interestingly, the switch from a for-loop to a mutableCopy for the array copy did not seem to improve things. If anything, mutableCopy was perhaps a 10th of a second slower (anyone done any benchmarking on mutableCopy?). But I left the change in because it is so much cleaner looking.

Lastly, note the check for album title == nil. Be aware that compare thinks anything with a nil value is always NSOrderedSame as anything else. One album in the list didn't have a title set, and this screwed up the sort order without this check.

MPMediaQuery *query = [MPMediaQuery playlistsQuery];
NSArray *playlists = [query collections];
NSMutableArray *songArray = [[NSMutableArray alloc] init];

for (MPMediaItemCollection *playlist in playlists) {
    NSString *playlistName = [playlist valueForProperty: MPMediaPlaylistPropertyName];
    NSLog (@"%@", playlistName);
    if ([playlistName isEqualToString:@"Last 4 months"]) {

        songArray = [[playlist items] mutableCopy ];

        [songArray sortUsingComparator:^NSComparisonResult(id a, id b) {
            NSComparisonResult compareResult;

            NSString *first1 = [(MPMediaItem*)a valueForProperty:MPMediaItemPropertyAlbumTitle];
            if(first1 == nil) first1 = @" "; // critical because compare will match nil to anything and result in NSOrderedSame
            NSString *second1 = [(MPMediaItem*)b valueForProperty:MPMediaItemPropertyAlbumTitle];
            if(second1 == nil) second1 = @" ";  // critical because compare will match nil to anything and result in NSOrderedSame
            compareResult = [first1 compare:second1];

            if (compareResult == NSOrderedSame) {
                NSString *first2 = [(MPMediaItem*)a valueForProperty:MPMediaItemPropertyAlbumPersistentID];
                NSString *second2 = [(MPMediaItem*)b valueForProperty:MPMediaItemPropertyAlbumPersistentID];
                compareResult = [first2 compare:second2];
                if(compareResult == NSOrderedSame) {
                    NSString *first3 = [(MPMediaItem*)a valueForProperty:MPMediaItemPropertyAlbumTrackNumber];
                    NSString *second3 = [(MPMediaItem*)b valueForProperty:MPMediaItemPropertyAlbumTrackNumber];
                    compareResult = [first3 compare:second3];
                }
            }
            return compareResult;
        }];
    }
}

Upvotes: 2

cdelacroix
cdelacroix

Reputation: 1289

You need to learn about sort descriptors.

Then your sort will be as simple as return [songs sortedArrayUsingDescriptors:sortDescriptors]; and should also be much faster.

EDIT: according to the OP, the MPMediaItem class is not KVC-compliant, so I'm retiring the answer.

Upvotes: 0

Related Questions