완성본
- 검색 화면 : 검색어에 맞는 곡들이 리스트로 보여진다
- 장르별 차트 화면 : 장르 별 차트 순위에 있는 곡들이 리스트로 보여진다
- 출시 앱 링크
1. 음악 API
- "음악 일기" 앱을 만들기 위해 가장 먼저 해야 할 일은 어떤 api를 사용할 지 정하기 이다.
- 정말 마지막까지 Spotify API와 Apple Music API 중 어떤 걸 사용해야 할 지 고민했다
Spotify API
- iOS SDK 가 있지만 일반적으로 Web API 를 주로 사용한다
- Web API Reference 사이트에 들어가면 직접 검색 결과를 확인할 수 있다
- 곡에 대한 장르 데이터를 주지 않고, 앨범에 대한 장르 데이터를 제공한다
Apple Music API
- apple music 을 구독한 상태라면 사용 범위가 엄청나게 넓어진다
- 앱 내에서 사용자의 재생목록이나 최근 곡 정보에 접근할 수도 있다
- 토큰 발급 과정이 복잡하고, 개발자 프로그램을 구매해야 사용이 가능하다
- 직접 통신을 하기 전에는 결과 데이터를 확인할 수가 없다..
- 결과적으로 Apple Music API를 선택했다
- 이유는 곡에 대한 장르 데이터를 얻어야 내가 원하는 서비스를 만들 수 있기 때문이었다. (고민에 비해 좀 싱거운 이유로 결정되었다..)
- Spotify API도 평소에 사용한 공공 API와는 사용법이 많이 달라 보여서 사용해보고 싶었기 때문에, 나중에 시간이 난다면 꼭 다시 도전해 보고 싶다
2. URL vs MusicKit
- 기존에 해봤던 네트워크 통신은 url 만 있으면 Alamofire 쓰고, APIManager 클래스 만들고, 뚝딱뚝딱 하면 결과값을 받을 수 있었기 때문에 이제 시작만 하면 된다고 생각했다.
- 그런데 apple music api에 대해 검색할 때마다 자꾸 MusicKit 이라는 프레임워크가 연관 검색어로 등장해서 자연스럽게 보게 되었다.
- 결과적으로 내가 정리한 바로는 기존에 apple music api를 사용하는 방식처럼
1. url을 이용한 네트워크 통신을 할 수도 있고,
2. MusicKit이라는 프레임워크를 이용한 네트워크 통신 을 할 수도 있었다.
- 어쨌든 둘 다 테스트해보고, 사용하기 편한 걸 선택해야지 생각했다
URL
-
장르 데이터를 가져오는 url을 이용한 네트워크 통신
import MusicKit
struct MyGenresResponse: Decodable {
let data: [Genre]
}
class APIManager {
static let shared = APIManager()
private init() { }
func callRequest() {
let url = URL(string: "https://api.music.apple.com/v1/catalog/kr/genres")!
AF.request(url).validate().responseDecodable(of: MyGenresResponse.self) { response in
print(response)
}
}
}
- 위 함수를 실행시키면 아래처럼 정상적인 응답을 받을 수 있었다
- 네트워크 통신을 생각보다 금방 성공해서, 어렵지 않게 나머지 데이터도 얻을 수 있다고 생각했다..
-
위 코드를 특정 장르만 검색하는 코드로 바꾼다면 아래 코드가 된다 (url 주소만 바뀐다)
func callRequest() {
let url = URL(string: "https://api.music.apple.com/v1/catalog/us/genres?ids=14,21")!
AF.request(url).validate().responseDecodable(of: MyGenresResponse.self) { response in
print(response)
}
}
- 위 함수를 실행시키면 아주 비정상적인 응답을 받게 된다
- 사실 401 에러가 뜨는 이유는 아주 간단한데, apple music api를 사용하기 위한 key 또는 token을 헤더에 넣어주지 않았기 때문이다 (라는 걸 지금 블로그 적으면서 깨달았다)
- 프로젝트를 진행할 당시는 MusicKit 함수와 alamofire를 사용하는 함수를 왔다갔다 하면서 테스트했기 때문에 MusicKit 프레임워크에 대한 토큰이 자동으로 발급되는 상황이었다 (토큰 발급에 대해서는 아래에서 설명한다)
- 그래서 당연히 나는 통신에 대한 권한(토큰)은 가지고 있기 때문에 왜 401 에러가 발생하는지 도저히 이해할 수가 없었다.
- 특히 모든 장르 데이터를 요청하는 url은 정상 응답이 오기 때문에 더 이해가 되지 않았다. ((추측) 지금 생각해 보면, 이 url만 sample로 권한 없이 응답을 받을 수 있도록 되어있는 것 같다.)
- 이것저것 시도해 본 끝에, 그 때 당시 나의 결론은 테스트하는 기기에서 애플 뮤직을 구독하고 있지 않기 때문에 401 에러가 뜨는 것이라고 판단했다.
- 이렇게 며칠 동안 스스로 착각의 늪에 깊숙하게 빠졌다. 지금 보니까 어떤 방식으로 다시 도전해야 할 지 보이는데, 그 때 당시에는 너무 답답해하면서 제대로 된 이유를 찾지 못했다.
- 그래서 다른 방법인 MusicKit 프레임워크 함수를 사용해야겠다고 결론지었다. (나도 애플뮤직을 구독하지 않기 때문에 테스트를 못하니까...)
3. MusicKit
- MusicKit을 사용하려고 할 때 가장 힘들었던 점은 한글 자료가 많지 않았다는 점이다
- 거의 WWDC 영상만 계속 돌려보면서 프레임워크에 대한 내용과 코드 레퍼런스를 공부했다
- 실질적으로 필요한 기능이 많지 않았기 때문에 다양한 함수를 써보진 못해서, 아마 앱 업데이트에 적용할 수 있는 기능이 어떤 게 있을지 추후에 고려해 볼 생각이다
참고
1). 코드 작성
class MusicKitManager {
static let shared = MusicKitManager()
private init() { }
func fetchMusic(_ txt: String) {
Task {
let status = await MusicAuthorization.request()
switch status {
case .authorized:
do {
var request = MusicCatalogSearchRequest(term: txt, types: [Song.self])
request.limit = 25
request.offset = 1
let result = try await request.response()
print(result)
} catch {
print(String(describing: error))
}
default:
break;
}
}
}
}
- 당당하게 위 코드를 실행시키면, 앱이 터진다
- MusicKit을 사용하기 위해서는 사용자의 apple music에 대해 접근해야 하기 때문에, 접근 권한에 대한 요청이 필요하다.
2). 접근 권한 설정
- Info.plist에서 Media Library에 대한 권한 허용을 요청한다
- 이제 앱을 실행시키면, 권한 요청에 대한 얼럿이 뜬다
- 당당하게 권한 허용을 해주면, 또 에러가 난다
- developerTokenRequestFailed
- 아직 요청에 대한 토큰이나 키를 발급받지도 않았기 때문에 데이터를 받지 못하는 게 당연하다
3). 개발자 토큰 생성
- 토큰을 생성하는 과정 때문에 대부분의 세션에서 MusicKit 활용을 적극 추천했다.
- 기본적으로 Apple Music API를 이용하기 위해선 개발자 토큰이 필요하다
- 만약 MusicKit을 이용하지 않는다면, 수동으로 직접 토큰을 발급받아야 한다 (주기적인 재발급도 필요하다) : generating developer token
- 처음 보는 것들이 많았는데, 아마 MusicKit이 아니었다면 이 방법으로 토큰을 발급받았을 것이다.
토큰 생성 과정
- 일단, 개발자 계정에 가입되어 있어야 한다 (깐부 비용 내야 한다..)
- Certificates, Identifiers & Profiles
- identifier 추가
- App Services에 MusicKit 추가 (bundle id 작성)
- 확인
- 등록이 끝나고 다시 함수를 실행해보면, 드디어 응답 데이터를 받을 수 있다!
4). 시간 이슈
- 응답 데이터를 받은 것까지는 너무 감사한데, 되게 찝찝한 이슈가 하나 생긴다.
- 데이터를 받을 때까지 속도가 너무 느리다
- (추측) 아마 데이터를 주기 전에 사용자의 Media Library에 대해 온갖 접근을 하기 때문에 그 과정에서 시간이 좀 걸리는 게 아닐까 하는 생각을 했다.
- 그래서 그런가, 첫 요청에 대한 속도는 정말 느린데 그 이후의 요청에 대한 속도는 전혀 문제가 되지 않는다
속도 테스트
- 첫 번째 요청에 대한 응답은 4초 후에 받아지는 걸 확인할 수 있다. 혼자 테스트를 정말 많이 했는데, 딜레이가 15초까지 발생한 적도 있었다
- 그에 반해 이후 요청에 대해서는 체감상 딜레이가 전혀 없는 수준으로 응답이 들어온다. 2번째를 포함한 이후 모든 요청에 대해서도 딜레이는 거의 없었다.
- 앱의 서비스 측면에서 보면, 이 딜레이는 아주 크리티컬한 이슈가 된다.
사용자가 음악을 검색했는데 10초 동안 검색 결과가 나오지 않는다면, 앱에 문제가 있다고 판단할 수 있기 때문이다. 아마 다시는 앱을 사용하지 않을 수도 있다.
- 어떻게 해야 이 시간을 줄일 수 있을까 열심히 고민했지만, 결론은 해결하지 못했다
- 그럼 앱을 포기할거냐? 그럴 순 없었고 약간의 꼼수(?)를 쓰기로 했다
- 앱을 실행하자마자 실행되는 함수(AppDelegate, SceneDelegate)에서 의미 없는 데이터를 요청한다
- 즉, 응답 딜레이를 아예 앱 초기에 발생시켜버리고, 실질적으로 사용자가 데이터를 요청할 때는 딜레이를 느끼지 못하게끔 코드를 구성했다.
- 절대 좋은 방법이라고 할 순 없지만, 생각나는 최선의 방법은 이 방법이었다..
4. 활용 기능
- 위 과정을 거쳐서 내 앱에 음악 검색 과 장르별 음악 차트 데이터를 받아오는 기능을 도입했다.
- MusicKit에 훨씬 더 많은 기능이 있지만, 출시 프로젝트 기간이 넉넉한 것이 아니기 때문에 욕심을 덜기로 했다. 아마 업데이트에 추가할 기능이 꽤 있을 듯 싶다
장르별 차트 코드
var genre: Genre?
func fetchInitialMusic() {
Task {
var request = MusicCatalogChartsRequest(
genre: genre,
kinds: [.cityTop],
types: [Song.self]
)
request.limit = 25
request.offset = 0
let result = try await request.response()
}
}
기억나는 이슈
- 네트워크 요청 시 파라미터로 주는
limit
과 offset
이 있는데, 당연하게 offset = 1
로 주고 응답을 받았다.
- 그런데 검색을 할 때마다 내가 원하는 곡이 안나오고 해당 곡의 리믹스 등 다른 버전만 결과로 나오길래, 애플 뮤직이 좀 특이하다고 생각했었다...
- 결론은
offset = 0
부터 시작하기 때문이었고, 원하는 곡 빼고 모든 곡을 혼자 검색하고 있었다.
- 혼자 이슈를 찾았을 때 정말 식겁했다... 가장 필수 기능인데 가장 문제있는 기능으로 출시할 뻔 했기 때문이다
- api 문서를 자세히 봐야 한다는 교훈을 얻게 된 경험이었다.