MPMusicPlayer队列中MPMediaItem执行数组函数速度过慢如何解决?
问题本质
这是MPMusicPlayerController队列相关API的已知实现缺陷,并非你的代码逻辑问题。从MPMusicPlayerControllerMutableQueue和MPMusicPlayerControllerQueue中获取的items集合不是普通本地数组,而是绑定系统媒体服务的懒加载代理集合:每次访问集合内MPMediaItem的属性(如artist)时,都会触发一次跨进程通信拉取数据,单元素访问开销是本地MPMediaItem的数百倍,批量遍历自然会出现数秒的延迟。
你从本地歌单获取的[MPMediaItem]是已经预加载完全部属性的本地对象,所以遍历速度正常。
解决方案
- 方案1:批量预拉取本地完整
MPMediaItem对象
不要直接对队列返回的items做过滤、映射操作,先通过persistentID批量匹配本地媒体库的预加载对象,再执行后续操作,代码示例:var musicPlayerController = MPMusicPlayerController.applicationQueuePlayer // 提前维护串行队列避免perform调用冲突 private let playerQueue = DispatchQueue(label: "com.yourapp.musicplayer.serial") playerQueue.async { [weak self] in self?.musicPlayerController.perform { (currentQueue) in // 仅提取persistentID,属性访问开销远小于访问歌曲元数据 let ids = currentQueue.items.compactMap { $0.persistentID } // 用MPMediaQuery一次性拉取本地预加载的完整对象 let query = MPMediaQuery.songs() query.addFilterPredicate(MPMediaPropertyPredicate( value: ids, forProperty: MPMediaItemPropertyPersistentID )) guard let localItems = query.items else { return } // 对本地数组做过滤,耗时低于10ms let itemsToRemove = localItems.filter { $0.artist == "Some artist" } if let targetItem = itemsToRemove.first, let queueItem = currentQueue.items.first(where: { $0.persistentID == targetItem.persistentID }) { currentQueue.remove(queueItem) } } completionHandler: { (newQueue, error) in if let e = error { print(e) return } // 同样用id匹配本地数组做映射 let newIds = newQueue.items.compactMap { $0.persistentID } let newLocalItems = query.items?.filter { newIds.contains($0.persistentID) } ?? [] tracks = newLocalItems.map { Track(item: $0) } } } - 方案2:独立维护本地队列缓存
不要依赖系统播放器的队列作为唯一数据源,在本地维护一份和播放器队列同步的[MPMediaItem]缓存,所有用户操作先修改本地缓存,再批量同步到播放器队列。既可以避免遍历播放器队列的开销,也可以通过串行队列修改缓存彻底解决数据竞争问题。 - 方案3:避免全量遍历操作
如果是已知位置的队列操作(比如用户点击删除列表第3行的歌曲),直接调用currentQueue.remove(at: index),不需要遍历匹配元素,完全规避懒加载集合的性能问题。
数据竞争规避
所有对perform方法的调用都统一提交到同一个串行队列执行,禁止并发调用perform,可以完全避免快速操作导致的队列状态混乱崩溃问题。
内容的提问来源于stack exchange,提问作者Jack Vanderpump
相关产品推荐
相关产品推荐

