skip to Main Content

I am trying to run the following function from SKCloudServiceController but for some reason every time it runs, the app just freezes. I have tested my developer token and it does work. I am running Xcode 12.2. Maybe there was an update which would make this not work anymore?

I’ve tested the token and it works.

class AppleMusicAPI {
    let developerToken = "b'eyJ0{...}RDlRSlFw'"

    func getUserToken() -> String {
        var userToken = String()
        let lock = DispatchSemaphore(value: 0)
        func requestAccess(_ completion: @escaping(String?) -> Void) {
            SKCloudServiceController().requestUserToken(forDeveloperToken: developerToken) { (receivedToken, error) in
                completion(receivedToken)
            }
        }
        requestAccess( { (completeToken) in
            if let token = completeToken {
                userToken = token
                lock.signal()
            }
        })
        lock.wait()
        return userToken
    }

    func fetchStorefrontID() -> String {
        let lock = DispatchSemaphore(value: 0)
        var storefrontID: String!
        let musicURL = URL(string: "https://api.music.apple.com/v1/me/storefront")!
        var musicRequest = URLRequest(url: musicURL)
        musicRequest.httpMethod = "GET"
        musicRequest.addValue("Bearer (developerToken)", forHTTPHeaderField: "Authorization")
        musicRequest.addValue(getUserToken(), forHTTPHeaderField: "Music-User-Token")
        
        URLSession.shared.dataTask(with: musicRequest) { (data, response, error) in
            guard error == nil else { return }
            
            if let json = try? JSON(data: data!) {
                let result = (json["data"]).array!
                let id = (result[0].dictionaryValue)["id"]!
                storefrontID = id.stringValue
                lock.signal()
            }
        }.resume()
        
        lock.wait()
        return storefrontID
    }
    
    func searchAppleMusic(_ searchTerm: String!) -> [Song] {
        let lock = DispatchSemaphore(value: 0)
        var songs = [Song]()

        let musicURL = URL(string: "https://api.music.apple.com/v1/catalog/(fetchStorefrontID())/search?term=(searchTerm.replacingOccurrences(of: " ", with: "+"))&types=songs&limit=25")!
        var musicRequest = URLRequest(url: musicURL)
        musicRequest.httpMethod = "GET"
        musicRequest.addValue("Bearer (developerToken)", forHTTPHeaderField: "Authorization")
        musicRequest.addValue(getUserToken(), forHTTPHeaderField: "Music-User-Token")
        
        URLSession.shared.dataTask(with: musicRequest) { (data, response, error) in
            guard error == nil else { return }
            if let json = try? JSON(data: data!) {
                let result = (json["results"]["songs"]["data"]).array!
                for song in result {
                    let attributes = song["attributes"]
                    let currentSong = Song(id: attributes["playParams"]["id"].string!, name: attributes["name"].string!, artistName: attributes["artistName"].string!, artworkURL: attributes["artwork"]["url"].string!)
                    songs.append(currentSong)
                }
                lock.signal()
            } else {
                lock.signal()
            }
        }.resume()
        
        lock.wait()
        return songs
    }
}

2

Answers


  1. I have a theory on what happened: since the requestUserToken function is called on the main thread, using a semaphore creates an infinite wait(lock.wait() and lock.signal() are called on the same thread). What eventually worked for me was using completion handlers instead of semaphores. So my getUserToken function looked like this:

    func getUserToken(completion: @escaping(_ userToken: String) -> Void) -> String {
        SKCloudServiceController().requestUserToken(forDeveloperToken: developerToken) { (userToken, error) in
              guard error == nil else {
                   return
              }
              completion(userToken)
        }
    }
    

    And in any subsequent functions that need the userToken, I passed it in as a parameter:

    func fetchStorefrontID(userToken: String, completion: @escaping(String) -> Void){
         var storefrontID: String!
         let musicURL = URL(string: "https://api.music.apple.com/v1/me/storefront")!
         var musicRequest = URLRequest(url: musicURL)
         musicRequest.httpMethod = "GET"
         musicRequest.addValue("Bearer (developerToken)", forHTTPHeaderField: "Authorization")
         musicRequest.addValue(userToken, forHTTPHeaderField: "Music-User-Token")
            
         URLSession.shared.dataTask(with: musicRequest) { (data, response, error) in
              guard error == nil else { return }
                
              if let json = try? JSON(data: data!) {
                  let result = (json["data"]).array!
                  let id = (result[0].dictionaryValue)["id"]!
                  storefrontID = id.stringValue
                  completion(storefrontID)
              }
         }.resume() 
    }
    

    Calling fetchStorefrontID by first calling getUserToken then calling fetchStorefrontID in its completion handler

    getUserToken{ userToken in
        fetchStorefrontID(userToken){ storefrontID in
            print(storefrontID)
            //anything you want to do with storefrontID here
        }
    }
    

    This is just what eventually worked for me.

    Login or Signup to reply.
  2. Cleaning up a little of what has already been posted.

    func getUserToken(completion: @escaping(_ userToken: String?) -> Void) {
        SKCloudServiceController().requestUserToken(forDeveloperToken: developerToken) { (receivedToken, error) in
            guard error == nil else { return }
    
            completion(receivedToken)
        }
    }
    
    func fetchStorefrontID(userToken: String, completion: @escaping(String) -> Void) {
        var storefrontID: String! = ""
    
        let musicURL = URL(string: "https://api.music.apple.com/v1/me/storefront")!
        var musicRequest = URLRequest(url: musicURL)
        musicRequest.httpMethod = "GET"
        musicRequest.addValue("Bearer (developerToken)", forHTTPHeaderField: "Authorization")
        musicRequest.addValue(userToken, forHTTPHeaderField: "Music-User-Token")
    
        URLSession.shared.dataTask(with: musicRequest) { (data, response, error) in
            guard error == nil else { return }
    
            if let json = try? JSON(data: data!) {
                let result = (json["data"]).array!
                let id = (result[0].dictionaryValue)["id"]!
                storefrontID = id.stringValue
                completion(storefrontID)
            }
        }.resume()
    }
    

    And then to call that code:

    SKCloudServiceController.requestAuthorization { status in
        if status == .authorized {
            let api = AppleMusicAPI()
            api.getUserToken { userToken in
                guard let userToken = userToken else {
                    return
                }
    
                api.fetchStorefrontID(userToken: userToken) { data in
                    print(data)
                }
            }
        }
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search