SKCloudServiceController().requestUserToken Freezes on iOS 14.2

718 views Asked by At

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

There are 2 answers

1
Marco Liang On

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.

0
Nick Kaczmarek On

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