MPMediaItemCollectionによる再生リストの保存

iPod Libraryとの連携においてはApple公式にあるAddMusicというサンプルが非常に秀逸で、これとiOS Referenceさえ見ておけば大体の事は解決しそうな感じで初心者の方には超おすすめなんですが、今回persistentIDまわりでちょっと危うい箇所があったのでブログに書き残しておきます。

AddMusic https://developer.apple.com/library/ios/#samplecode/AddMusic/

iPod Libraryとの連携で再生リストを作る場合って相当偏っている人でいなければMPMediaItemCollectionで管理すると思うんですが、内部的にunsigned long long で扱われているpersistentIDをNSNumberのままNSUserDefaultsで保存すると、読み込みに失敗するよっていう話です。

NSUserDefaultsの保存と読み込みにおいてはNSUserDefaults自身が型を判定している訳ではなくて、オブジェクトに対して「保存するから専用形式に変換した値を投げてくれよ」と言うんですが、persistentIDをNSNumberのまま保存するとNSNumberは自身をNSStringに変換して渡すようです。

さて、渡すまでは全然問題はなくて、問題は読み込みの際です。
persistentIDは内部的にはunsigned long longという型で保持されており、persistntIDからNSNumberの変換時には数値から数値の変換でうまくいくようですが、保存されたデータ読み込み時のNSStringからNSNumberに変換する際にunsigned long long と判定されないようです。

この問題については調べてる途中で下記の文献を見つけていたのでなんとか回避できました。非常にありがたいです。

MPMediaItemPropertyPersistentID検索でハマったこと

こちらのブログにあったコードを参考にしつつ保存と読み込み用関数を書いてみましたが、ここで重大発表。
アドバイスを頂いた@sawat1203さんの話によるとpersistentIDは一意ではなく、iTunesとの同期時に値が変化してしまうため再生リストの保存には向いていないようです...。
とりあえずpropertyとMPMediaItemCollectionの連携という点において大まかな機構は変わらないと思いますので、保存と読み込に関して私が書いたコード全文を下記に記載しておきます。さて、どのpropertyが一番信用できるのでしょうか、考えてみます。


// NSString から NSNumber(unsigned long long)への変換時に使用
unsigned long long myfanc(const char *p)
{
unsigned long long n = 0;
/* 数値の取得 */
while (isdigit(*p)) {
n = n * 10 + *p - '0';
p++;
}
/* 結果を返す */
return n;
}

// UserMediaItemCollectionの内容をUserDefaultsに保存する関数
(void)saveUserMediaItemCollection
{
NSLog(@"saveUserMediaItemCollection called.");

if([userMediaItemCollection count]){

NSMutableArray *persistentArray = [NSMutableArray array];

// userMediaItemのpersitentIDをNSMutableArray->NSStringに格納する
for(NSUInteger i = 0; i < [userMediaItemCollection count]; i++){
MPMediaItem *mediaItem = [[userMediaItemCollection items] objectAtIndex:i];
NSString *persistentID = [[NSString alloc] initWithFormat:@"%@",
[mediaItem valueForProperty: MPMediaItemPropertyPersistentID]];
[persistentArray addObject:persistentID];

//test code
NSLog(@"save persistentID = %@", persistentID);
}

// NSMutableArrayを保存する
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
[userDefaults setObject:persistentArray forKey:USER_MEDIA_ITEMS_KEY];
[userDefaults synchronize];

}
}

// UserMediaItemCollectionの内容をUserDefaultsから読み込む関数
(void)loadUserMediaItemCollection
{
NSLog(@"loadUserMediaItemCollection called.");
NSArray *loadMediaItems = [[NSUserDefaults standardUserDefaults] objectForKey:USER_MEDIA_ITEMS_KEY];

if(loadMediaItems){
NSLog(@"loadMediaItems is not nil");

// 取得した配列の全要素を復元する
for(NSUInteger i = 0; i < [loadMediaItems count]; i++){

// NSStringからNSNumberへ復元する
NSString *persistentString = [loadMediaItems objectAtIndex:i];
const char *pS= [persistentString UTF8String]; //C言語で使える文字列に変換する
unsigned long long pid_num = myfanc(pS); //変換する
NSNumber *persistentID = [NSNumber numberWithUnsignedLongLong:pid_num]; //Number型に直す

// for debug
NSLog(@"persistentID(NSNumber) = %@", persistentID);

// persistentIDをもとにqueryを生成しHitしたMPMediaItemをNSArrayに格納する
MPMediaQuery *query = [[MPMediaQuery alloc] init]; // songsQuery];
MPMediaPropertyPredicate * pred;
pred = [MPMediaPropertyPredicate predicateWithValue:persistentID
forProperty:MPMediaItemPropertyPersistentID
comparisonType:MPMediaPredicateComparisonEqualTo];
[query addFilterPredicate:pred];
NSArray *addMediaItems = [query items];

// HitしたMPMediaItemをuserMediaItemCollectionに格納する
if(addMediaItems){
if(userMediaItemCollection){
// queryから生成したアイテムと既存アイテムを結合して保存する
NSMutableArray *combinedMediaItems = [[userMediaItemCollection items] mutableCopy];
NSArray *addMediaItems = [query items];
[combinedMediaItems addObjectsFromArray: addMediaItems];
[self setUserMediaItemCollection: [MPMediaItemCollection collectionWithItems: (NSArray *) combinedMediaItems]];
}else{
// NSArray(MPMediaItem)からMPMediaItemCollectionを生成する
userMediaItemCollection = [[MPMediaItemCollection alloc] initWithItems:addMediaItems];
}
}

}

}
}