[새싹 iOS] 13주차_MusicKit

임승섭·2023년 10월 13일
2

새싹 iOS

목록 보기
27/45
post-thumbnail

완성본

  • 검색 화면 : 검색어에 맞는 곡들이 리스트로 보여진다
  • 장르별 차트 화면 : 장르 별 차트 순위에 있는 곡들이 리스트로 보여진다
  • 출시 앱 링크

1. 음악 API


  • "음악 일기" 앱을 만들기 위해 가장 먼저 해야 할 일은 어떤 api를 사용할 지 정하기 이다.
  • 정말 마지막까지 Spotify APIApple 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]	// Genre 타입을 사용하기 위해 MusicKit을 import 한다
    }
    
    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 {
            // Request permission
            let status = await MusicAuthorization.request()

            switch status {
            case .authorized:
                // Request -> Response
                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이 아니었다면 이 방법으로 토큰을 발급받았을 것이다.

토큰 생성 과정

  • 일단, 개발자 계정에 가입되어 있어야 한다 (깐부 비용 내야 한다..)
  1. Certificates, Identifiers & Profiles
  1. identifier 추가
  1. App Services에 MusicKit 추가 (bundle id 작성)
  1. 확인

  • 등록이 끝나고 다시 함수를 실행해보면, 드디어 응답 데이터를 받을 수 있다!

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()
        }
}

기억나는 이슈

  • 네트워크 요청 시 파라미터로 주는 limitoffset이 있는데, 당연하게 offset = 1 로 주고 응답을 받았다.
  • 그런데 검색을 할 때마다 내가 원하는 곡이 안나오고 해당 곡의 리믹스 등 다른 버전만 결과로 나오길래, 애플 뮤직이 좀 특이하다고 생각했었다...
  • 결론은 offset = 0부터 시작하기 때문이었고, 원하는 곡 빼고 모든 곡을 혼자 검색하고 있었다.
  • 혼자 이슈를 찾았을 때 정말 식겁했다... 가장 필수 기능인데 가장 문제있는 기능으로 출시할 뻔 했기 때문이다
  • api 문서를 자세히 봐야 한다는 교훈을 얻게 된 경험이었다.

0개의 댓글