Reputation: 549
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
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
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
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