iOS14.2调用SKCloudServiceController.requestUserToken致APP冻结求助
问题根源:主线程死锁导致APP冻结
你的APP之所以每次运行都会冻结,核心原因是你在主线程中使用DispatchSemaphore.wait()阻塞了线程,而异步回调无法触发signal(),最终造成死锁。
具体来说:
SKCloudServiceController.requestUserToken、URLSession.dataTask这类API都是异步执行的,它们的回调会在后台队列(或主线程队列)触发。- 当你在主线程调用
lock.wait()时,主线程会进入休眠状态,完全无法处理任何事件循环(包括UI更新、系统回调)。 - 如果
requestUserToken的回调默认需要回到主线程执行(很多系统API的回调都是如此),那主线程已经被wait()卡住了,永远无法执行回调里的lock.signal(),直接导致死锁——APP彻底失去响应。
这个问题和Xcode 12.2的更新无关,是异步代码同步化的典型错误用法。
修复方案:改用异步回调/Async/Await
正确的做法是放弃用信号量强行同步异步代码,改用异步回调或者iOS 15+支持的async/await,让主线程始终保持响应。
方案1:基于Completion Handler的异步重构
把所有同步方法改成带回调的异步版本,避免阻塞主线程:
class AppleMusicAPI { let developerToken = "b'eyJ0{...}RDlRSlFw'" // 重构getUserToken为异步回调 func getUserToken(completion: @escaping (String?) -> Void) { SKCloudServiceController().requestUserToken(forDeveloperToken: developerToken) { receivedToken, error in completion(receivedToken) } } // 重构fetchStorefrontID为异步回调,依赖getUserToken的结果 func fetchStorefrontID(completion: @escaping (String?) -> Void) { getUserToken { userToken in guard let userToken = userToken else { completion(nil) return } let musicURL = URL(string: "https://api.music.apple.com/v1/me/storefront")! var musicRequest = URLRequest(url: musicURL) musicRequest.httpMethod = "GET" musicRequest.addValue("Bearer \(self.developerToken)", forHTTPHeaderField: "Authorization") musicRequest.addValue(userToken, forHTTPHeaderField: "Music-User-Token") URLSession.shared.dataTask(with: musicRequest) { data, response, error in guard error == nil, let data = data else { completion(nil) return } if let json = try? JSON(data: data) { let storefrontID = json["data"].array?[0]["id"].stringValue completion(storefrontID) } else { completion(nil) } }.resume() } } // 重构searchAppleMusic为异步回调 func searchAppleMusic(_ searchTerm: String, completion: @escaping ([Song]) -> Void) { fetchStorefrontID { storefrontID in guard let storefrontID = storefrontID, !searchTerm.isEmpty else { completion([]) return } let encodedTerm = searchTerm.replacingOccurrences(of: " ", with: "+") let musicURL = URL(string: "https://api.music.apple.com/v1/catalog/\(storefrontID)/search?term=\(encodedTerm)&types=songs&limit=25")! var musicRequest = URLRequest(url: musicURL) musicRequest.httpMethod = "GET" musicRequest.addValue("Bearer \(self.developerToken)", forHTTPHeaderField: "Authorization") self.getUserToken { userToken in guard let userToken = userToken else { completion([]) return } musicRequest.addValue(userToken, forHTTPHeaderField: "Music-User-Token") URLSession.shared.dataTask(with: musicRequest) { data, response, error in guard error == nil, let data = data else { completion([]) return } var songs = [Song]() if let json = try? JSON(data: data) { let songResults = json["results"]["songs"]["data"].array ?? [] for song in songResults { let attributes = song["attributes"] guard let songID = attributes["playParams"]["id"].string, let songName = attributes["name"].string, let artistName = attributes["artistName"].string, let artworkURL = attributes["artwork"]["url"].string else { continue } let currentSong = Song(id: songID, name: songName, artistName: artistName, artworkURL: artworkURL) songs.append(currentSong) } } completion(songs) }.resume() } } } }
调用示例:
let api = AppleMusicAPI() api.searchAppleMusic("Hello") { songs in // 拿到结果后回到主线程更新UI DispatchQueue.main.async { // 处理歌曲数据,比如刷新列表 } }
方案2:用Async/Await简化代码(iOS 15+)
如果你的项目支持iOS 15及以上,用async/await会让代码逻辑更清晰,可读性更高:
class AppleMusicAPI { let developerToken = "b'eyJ0{...}RDlRSlFw'" func getUserToken() async -> String? { await withCheckedContinuation { continuation in SKCloudServiceController().requestUserToken(forDeveloperToken: developerToken) { token, error in continuation.resume(returning: token) } } } func fetchStorefrontID() async -> String? { guard let userToken = await getUserToken() else { return nil } 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") do { let (data, _) = try await URLSession.shared.data(for: musicRequest) let json = try JSON(data: data) return json["data"].array?[0]["id"].stringValue } catch { return nil } } func searchAppleMusic(_ searchTerm: String) async -> [Song] { guard let storefrontID = await fetchStorefrontID(), !searchTerm.isEmpty else { return [] } let encodedTerm = searchTerm.replacingOccurrences(of: " ", with: "+") guard let musicURL = URL(string: "https://api.music.apple.com/v1/catalog/\(storefrontID)/search?term=\(encodedTerm)&types=songs&limit=25") else { return [] } var musicRequest = URLRequest(url: musicURL) musicRequest.httpMethod = "GET" musicRequest.addValue("Bearer \(developerToken)", forHTTPHeaderField: "Authorization") guard let userToken = await getUserToken() else { return [] } musicRequest.addValue(userToken, forHTTPHeaderField: "Music-User-Token") do { let (data, _) = try await URLSession.shared.data(for: musicRequest) let json = try JSON(data: data) let songResults = json["results"]["songs"]["data"].array ?? [] return songResults.compactMap { song in let attributes = song["attributes"] guard let id = attributes["playParams"]["id"].string, let name = attributes["name"].string, let artistName = attributes["artistName"].string, let artworkURL = attributes["artwork"]["url"].string else { return nil } return Song(id: id, name: name, artistName: artistName, artworkURL: artworkURL) } } catch { return [] } } }
调用示例:
let api = AppleMusicAPI() Task { let songs = await api.searchAppleMusic("Hello") // 回到主线程更新UI DispatchQueue.main.async { // 处理歌曲数据 } }
关键提醒
永远不要在主线程调用DispatchSemaphore.wait(),这是阻塞主线程的大忌,会直接导致APP无响应。所有涉及网络请求、系统异步API的操作,都应该用异步方式处理,让主线程专注于UI渲染和用户交互。
内容的提问来源于stack exchange,提问作者Papi
相关产品推荐
相关产品推荐

