본문 바로가기
UIKit Project/UIKit Troubleshooting

[LilsMusic] MusicKit의 Swift Concurrency, Combine과 RxSwift 연동하며 생긴 트러블 슈팅

by thekoon 2024. 3. 31.

MusicKit을 활용한 음악앱을 만들기로 결정하고 한 가지 걱정되는 사실이 있었습니다.

그것을 바로..! MusicKit이 비교적 최신 API이고, SwiftUI를 대상으로 만든 API이기 때문에 

대부분의 비동기 함수가 async / await함수이고, 이번에 제가 사용할 기술스택은 UIKit + RxSwift이기 때문에

잘 연동해서 만들 수 있을까는 걱정이 들었습니다.

 

만들면서 몇 가지 트러블이 있었고, 어떻게 해결했는지 정리해보려 합니다.

 

 

1. 다양한 Player시점의 부재

재생되던 음악이 끝나고 다음 노래가 재생될때의 시점을 파악해 화면에 바뀐 노래의 정보를 표시해줘야 했습니다.

 

기존의 음악플레이어인 MPMusicPlayer는 오래 사용한 만큼 다양한 시점의 Notification을 제공해줍니다.

https://developer.apple.com/documentation/mediaplayer/mpmusicplayercontroller

 

MPMusicPlayerController | Apple Developer Documentation

An object that plays audio media items from the device’s Music app library.

developer.apple.com

 

 

그런데 왜!! MusicKit은 Combine으로만, 그것도 ObjectWillChange 시점 하나만 주는걸까요..

제가 하고 싶은 것은 노래가 넘어갔을때 넘어간 노래의 entry를 알고 싶었기 때문에 WillChange가 아닌 DidChange 시점을 알고 싶었는데, MusicKit에서 제공해주는 시점이 willChange 시점뿐이라 노래가 바뀐 뒤의 시점을 바로 알기가 어려웠습니다.

게다가 접근제어자로 해당 클래스의 접근이 막혀있고, 상속도 받지 못하기 때문에 제가 didSet등을 활용할 수 있는 프로퍼티를 추가하기도 어려웠습니다.

 

그래서 곡의 정보를 얻는 시점을 늦추기도 해보고 

 

쓰로틀을 걸어서 업데이트 시점을 늦춰보기도 하다가

 

결과적으로 debounce를 조금 줘서 연속적으로 오는 상태값이 끝났을때부터 0.2초 뒤, 바뀐 엔트리를 entrySubject로 보내고,

다른 뷰모델에서 이 Subject를 옵저버블로 구독, 업데이트하도록 만들었습니다.

이렇게 만들면서 헷갈리던 쓰로틀과 디바운스에 대해 확실하게 이해할 수 있게 되어서 결과적으로는 좋았습니다..ㅎㅎ

 

2. 스레드 관리

화면에 그려주기 위해서는 entry의 id로 서버요청을 한 뒤 나온 결과물로 화면을 그려줘야하는데

subscribe를 하면서 observe(on:)함수로 스레드를 설정하더라도

Task { } 구문 안에서는 해당 스레드가 아닌 다른 context에서 동작할 것이라는 생각이 들었습니다.

게다가 현재 곡을 가져오는 함수 말고도

노래 재생, 일시정지, 다음곡, 이전곡, 재생Queue 삽입, 변경 등 다양한 관련 함수가 있고, 연결되어 있기 때문에

스레드 관리하기가 쉽지 않았습니다.

 

앱을 테스트하다보면 간헐적으로 스레드 문제가 발생했는데

어디서 문제가 일어나는지 알기 힘들어서 고치는데 어려움을 많이 받았습니다.

 

MusicKit의 음악플레이어의 경우 재생, 정지, 큐 설정 등은 메인 스레드에서 동작해야하고,

Queue를 설정한 엔트리를 화면에 그리거나, 다양한 플레이리스트, 앨범점보 등을 가져오는 것은 

네트워크 통신을 통해야 하므로 백그라운드 스레드에서 동작해야합니다.

그래서 생각했던게 필요한 작업을 Task에서 하고, 마친 결과물을 Observable로 만들어서 구독하면, 그 이후에 스레드 관리를 기존 rxSwift 방식으로 할 수 있겠다는 생각이 들었습니다.

 

우선적으로 MusicPlayerManager에서 async함수는 @mainActor, 일반 함수는 DispatchQueue로 메인스레드로 설정을 해줬습니다.

 

그리고 combine으로 받은 Entry를 Subject로 -> 가져온 entry를 다른 함수에서 처리하고, 결과물을 Observable로 반환하도록 flatMap을 통해서 구성해주고, 받은 결과물을 구독해서 사용하는 방식으로 구성했습니다.

그리고 저 trackSubject를 asDriver로 변환하는 Output을 만들면

 Output을 받은 뷰컨트롤러에서는 drive 함수를 통해 간단히 UI를 업데이트할 수 있도록 구성했습니다.

 

이 방식으로 MusicKit에서 제공하는 여러 async/await 함수를 rxSwift의 Observable로 관리하면서, 스레드 관리도 기존 방식으로 할 수 있도록 구성해보았습니다. 그 이후에는 스레드 관련 오류는 나지 않았고, 동작에도 문제없는 모습을 보였습니다.

다음 글에서 다른 트러블슈팅으로 찾아오겠습니다

감사합니다!