无法使用 SwiftUI 中的 Combine 框架从 Spotify API 获取数据
Couldn't not fetching the data from Spotify API with Combine framework in SwiftUI
这是我的想法,我想在用户通过$artistName
搜索时呈现数据,然后组合框架可以帮助我从服务器请求数据。
我不知道我错在哪一步了。当我尝试通过模拟器获取数据时。它将显示未知错误和其他错误。
2020-10-23 03:56:38.220523+0800 PodcastSearchV2[42967:667554] [] nw_protocol_get_quic_image_block_invoke dlopen libquic failed
Fetch failed: Unknown error
2020-10-23 03:56:40.114906+0800 PodcastSearchV2[42967:667546] [connection] nw_connection_copy_protocol_metadata [C2] Client called nw_connection_copy_protocol_metadata on unconnected nw_connection
2020-10-23 03:56:40.115071+0800 PodcastSearchV2[42967:667546] [connection] nw_connection_copy_protocol_metadata [C2] Client called nw_connection_copy_protocol_metadata on unconnected nw_connection
2020-10-23 03:56:40.115255+0800 PodcastSearchV2[42967:667546] [connection] nw_connection_copy_protocol_metadata [C2] Client called nw_connection_copy_protocol_metadata on unconnected nw_connection
2020-10-23 03:56:40.115360+0800 PodcastSearchV2[42967:667546] [connection] nw_connection_copy_protocol_metadata [C2] Client called nw_connection_copy_protocol_metadata on unconnected nw_connection
2020-10-23 03:56:40.115516+0800 PodcastSearchV2[42967:667546] [connection] nw_connection_copy_connected_local_endpoint [C2] Client called nw_connection_copy_connected_local_endpoint on unconnected nw_connection
2020-10-23 03:56:40.115613+0800 PodcastSearchV2[42967:667546] [connection] nw_connection_copy_connected_remote_endpoint [C2] Client called nw_connection_copy_connected_remote_endpoint on unconnected nw_connection
2020-10-23 03:56:40.115723+0800 PodcastSearchV2[42967:667546] [connection] nw_connection_copy_connected_path [C2] Client called nw_connection_copy_connected_path on unconnected nw_connection
2020-10-23 03:56:40.115924+0800 PodcastSearchV2[42967:667546] Connection 2: unable to determine interface type without an established connection
2020-10-23 03:56:40.116069+0800 PodcastSearchV2[42967:667546] Connection 2: unable to determine interface classification without an established connection
2020-10-23 03:56:40.116381+0800 PodcastSearchV2[42967:667546] [connection] nw_connection_copy_protocol_metadata [C2] Client called nw_connection_copy_protocol_metadata on unconnected nw_connection
2020-10-23 03:56:40.120441+0800 PodcastSearchV2[42967:667546] [connection] nw_connection_copy_metadata [C2] Client called nw_connection_copy_metadata on unconnected nw_connection
2020-10-23 03:56:40.120579+0800 PodcastSearchV2[42967:667546] Connection 2: unable to determine interface type without an established connection
2020-10-23 03:56:40.121823+0800 PodcastSearchV2[42967:667546] Connection 2: unable to determine interface type without an established connection
这是我的回复。
struct DataResponseSpotify: Codable {
var episodes: PodcastItemSpotify
}
struct PodcastItemSpotify: Codable {
var items: [PodcastItemsDetailsSpotify]
}
struct PodcastItemsDetailsSpotify: Codable, Identifiable {
let id: String
let description: String
let images: [Images]
let name: String
let external: String
var externalURL: URL? {
return URL(string: external)
}
enum CodingKeys: String, CodingKey {
case id
case description
case images
case name
case external = "external_urls"
}
}
struct Images: Codable {
let url: String
var imageURL: URL? {
return URL(string: url)
}
enum CodingKeys: String, CodingKey {
case url
}
}
struct Token: Codable {
let accessToken: String
enum CodingKeys: String, CodingKey {
case accessToken = "access_token"
}
}
我在模型中是这样写的
class DataObserverSpotify: ObservableObject {
@Published var artistName = ""
@Published var token = ""
@Published var fetchResult = [PodcastItemsDetailsSpotify]()
var subscriptions: Set<AnyCancellable> = []
let podsURLComponents = PodsFetcher()
init() {
getToken()
$artistName
.debounce(for: .seconds(2), scheduler: RunLoop.main)
.removeDuplicates()
.compactMap { query in
let url = self.podsURLComponents.makeURLComponentsForSpotify(withName: query, tokenAccess: self.token)
return URL(string: url.string ?? "")
}
.flatMap(fetchDatatesting)
.receive(on: DispatchQueue.main)
.assign(to: \.fetchResult, on: self)
.store(in: &subscriptions)
}
func fetchDatatesting(url: URL) -> AnyPublisher<[PodcastItemsDetailsSpotify], Never> {
URLSession.shared.dataTaskPublisher(for: url)
.map(\.data)
.decode(type: DataResponseSpotify.self, decoder: JSONDecoder())
.map(\.episodes.items)
.replaceError(with: [])
.eraseToAnyPublisher()
}
func getToken() {
let parameters = "grant_type=refresh_token&refresh_token=[example-refresh-token]"
let postData = parameters.data(using: .utf8)
var request = URLRequest(url: URL(string: "https://accounts.spotify.com/api/token")!,timeoutInterval: Double.infinity)
request.addValue("Basic exampleBasicAuth=", forHTTPHeaderField: "Authorization")
request.addValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
request.addValue("inapptestgroup=; __Host-device_id=example_id; __Secure-example=; csrf_token=example", forHTTPHeaderField: "Cookie")
request.httpMethod = "POST"
request.httpBody = postData
URLSession.shared.dataTask(with: request) { data, response, error in
if let data = data {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
if let token = try? decoder.decode(Token.self, from: data) {
DispatchQueue.main.async {
self.token = token.accessToken
}
return
}
}
print("Fetch failed: \(error?.localizedDescription ?? "Unknown error")")
}.resume()
}
}
您调用 getToken
设置令牌 异步 ,但无需等待,您立即尝试创建 URL 假设令牌在 .compactMap
- 到那时它还不可用。
因此,您必须“等待”它,而对于 Combine,您可以通过多种方式做到这一点。
最简单(最便宜)的更改是将 $token
与 $artistName
发布商合并:
$artistName
.debounce(for: .seconds(2), scheduler: RunLoop.main)
.removeDuplicates()
.combineLatest($token.filter { ![=10=].isEmpty }) // waits for a non-empty token
.compactMap { (query, token) in
var url = ... // construct URL from query and token
return url
}
.flatMap(fetchDatatesting)
.receive(on: DispatchQueue.main)
// I used sink, since assign causes a self retain cycle
.sink { [weak self] result in self?.fetchResult = result }
.store(in: &subscriptions)
但是你真的应该重新设计你的 getToken
方法 - 它写得不好,因为它是一个异步函数,但它没有完成处理程序。
例如,像这样:
func getToken(_ completion: @escaping (Token) -> Void) {
// set-up, etc..
URLSession.shared.dataTask(with: request) { data, response, error in
// error handling, etc...
if let token = try? decoder.decode(Token.self, from: data) {
completion(token)
}
}.resume()
}
// usage
getToken {
self.token = [=12=]
}
或者,由于您使用的是 Combine,您可以 re-write 将其发送给 return 发布者(为了简洁起见,我忽略了此处的错误):
func getToken() -> AnyPublisher<Token?, Never> {
// ...
}
这是我的想法,我想在用户通过$artistName
搜索时呈现数据,然后组合框架可以帮助我从服务器请求数据。
我不知道我错在哪一步了。当我尝试通过模拟器获取数据时。它将显示未知错误和其他错误。
2020-10-23 03:56:38.220523+0800 PodcastSearchV2[42967:667554] [] nw_protocol_get_quic_image_block_invoke dlopen libquic failed
Fetch failed: Unknown error
2020-10-23 03:56:40.114906+0800 PodcastSearchV2[42967:667546] [connection] nw_connection_copy_protocol_metadata [C2] Client called nw_connection_copy_protocol_metadata on unconnected nw_connection
2020-10-23 03:56:40.115071+0800 PodcastSearchV2[42967:667546] [connection] nw_connection_copy_protocol_metadata [C2] Client called nw_connection_copy_protocol_metadata on unconnected nw_connection
2020-10-23 03:56:40.115255+0800 PodcastSearchV2[42967:667546] [connection] nw_connection_copy_protocol_metadata [C2] Client called nw_connection_copy_protocol_metadata on unconnected nw_connection
2020-10-23 03:56:40.115360+0800 PodcastSearchV2[42967:667546] [connection] nw_connection_copy_protocol_metadata [C2] Client called nw_connection_copy_protocol_metadata on unconnected nw_connection
2020-10-23 03:56:40.115516+0800 PodcastSearchV2[42967:667546] [connection] nw_connection_copy_connected_local_endpoint [C2] Client called nw_connection_copy_connected_local_endpoint on unconnected nw_connection
2020-10-23 03:56:40.115613+0800 PodcastSearchV2[42967:667546] [connection] nw_connection_copy_connected_remote_endpoint [C2] Client called nw_connection_copy_connected_remote_endpoint on unconnected nw_connection
2020-10-23 03:56:40.115723+0800 PodcastSearchV2[42967:667546] [connection] nw_connection_copy_connected_path [C2] Client called nw_connection_copy_connected_path on unconnected nw_connection
2020-10-23 03:56:40.115924+0800 PodcastSearchV2[42967:667546] Connection 2: unable to determine interface type without an established connection
2020-10-23 03:56:40.116069+0800 PodcastSearchV2[42967:667546] Connection 2: unable to determine interface classification without an established connection
2020-10-23 03:56:40.116381+0800 PodcastSearchV2[42967:667546] [connection] nw_connection_copy_protocol_metadata [C2] Client called nw_connection_copy_protocol_metadata on unconnected nw_connection
2020-10-23 03:56:40.120441+0800 PodcastSearchV2[42967:667546] [connection] nw_connection_copy_metadata [C2] Client called nw_connection_copy_metadata on unconnected nw_connection
2020-10-23 03:56:40.120579+0800 PodcastSearchV2[42967:667546] Connection 2: unable to determine interface type without an established connection
2020-10-23 03:56:40.121823+0800 PodcastSearchV2[42967:667546] Connection 2: unable to determine interface type without an established connection
这是我的回复。
struct DataResponseSpotify: Codable {
var episodes: PodcastItemSpotify
}
struct PodcastItemSpotify: Codable {
var items: [PodcastItemsDetailsSpotify]
}
struct PodcastItemsDetailsSpotify: Codable, Identifiable {
let id: String
let description: String
let images: [Images]
let name: String
let external: String
var externalURL: URL? {
return URL(string: external)
}
enum CodingKeys: String, CodingKey {
case id
case description
case images
case name
case external = "external_urls"
}
}
struct Images: Codable {
let url: String
var imageURL: URL? {
return URL(string: url)
}
enum CodingKeys: String, CodingKey {
case url
}
}
struct Token: Codable {
let accessToken: String
enum CodingKeys: String, CodingKey {
case accessToken = "access_token"
}
}
我在模型中是这样写的
class DataObserverSpotify: ObservableObject {
@Published var artistName = ""
@Published var token = ""
@Published var fetchResult = [PodcastItemsDetailsSpotify]()
var subscriptions: Set<AnyCancellable> = []
let podsURLComponents = PodsFetcher()
init() {
getToken()
$artistName
.debounce(for: .seconds(2), scheduler: RunLoop.main)
.removeDuplicates()
.compactMap { query in
let url = self.podsURLComponents.makeURLComponentsForSpotify(withName: query, tokenAccess: self.token)
return URL(string: url.string ?? "")
}
.flatMap(fetchDatatesting)
.receive(on: DispatchQueue.main)
.assign(to: \.fetchResult, on: self)
.store(in: &subscriptions)
}
func fetchDatatesting(url: URL) -> AnyPublisher<[PodcastItemsDetailsSpotify], Never> {
URLSession.shared.dataTaskPublisher(for: url)
.map(\.data)
.decode(type: DataResponseSpotify.self, decoder: JSONDecoder())
.map(\.episodes.items)
.replaceError(with: [])
.eraseToAnyPublisher()
}
func getToken() {
let parameters = "grant_type=refresh_token&refresh_token=[example-refresh-token]"
let postData = parameters.data(using: .utf8)
var request = URLRequest(url: URL(string: "https://accounts.spotify.com/api/token")!,timeoutInterval: Double.infinity)
request.addValue("Basic exampleBasicAuth=", forHTTPHeaderField: "Authorization")
request.addValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
request.addValue("inapptestgroup=; __Host-device_id=example_id; __Secure-example=; csrf_token=example", forHTTPHeaderField: "Cookie")
request.httpMethod = "POST"
request.httpBody = postData
URLSession.shared.dataTask(with: request) { data, response, error in
if let data = data {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
if let token = try? decoder.decode(Token.self, from: data) {
DispatchQueue.main.async {
self.token = token.accessToken
}
return
}
}
print("Fetch failed: \(error?.localizedDescription ?? "Unknown error")")
}.resume()
}
}
您调用 getToken
设置令牌 异步 ,但无需等待,您立即尝试创建 URL 假设令牌在 .compactMap
- 到那时它还不可用。
因此,您必须“等待”它,而对于 Combine,您可以通过多种方式做到这一点。
最简单(最便宜)的更改是将 $token
与 $artistName
发布商合并:
$artistName
.debounce(for: .seconds(2), scheduler: RunLoop.main)
.removeDuplicates()
.combineLatest($token.filter { ![=10=].isEmpty }) // waits for a non-empty token
.compactMap { (query, token) in
var url = ... // construct URL from query and token
return url
}
.flatMap(fetchDatatesting)
.receive(on: DispatchQueue.main)
// I used sink, since assign causes a self retain cycle
.sink { [weak self] result in self?.fetchResult = result }
.store(in: &subscriptions)
但是你真的应该重新设计你的 getToken
方法 - 它写得不好,因为它是一个异步函数,但它没有完成处理程序。
例如,像这样:
func getToken(_ completion: @escaping (Token) -> Void) {
// set-up, etc..
URLSession.shared.dataTask(with: request) { data, response, error in
// error handling, etc...
if let token = try? decoder.decode(Token.self, from: data) {
completion(token)
}
}.resume()
}
// usage
getToken {
self.token = [=12=]
}
或者,由于您使用的是 Combine,您可以 re-write 将其发送给 return 发布者(为了简洁起见,我忽略了此处的错误):
func getToken() -> AnyPublisher<Token?, Never> {
// ...
}