如何从视图中检测 URLSession 错误并做出相应反应?
How to detect URLSession Errors from the view and react accordingly?
所以我正在编写一个简单的歌词应用程序,它使用 API 接收歌曲名称和艺术家以及 returns 歌词。如果我发送正确的歌曲名称和艺术家,一切都会按预期进行,但我很难从视图中检测到错误并做出相应的反应,例如在 sheet 被渲染之前显示警报之类的。我的意思是错误,例如用户输入的歌曲或艺术家姓名打错了,这会使 API 无法获取此类歌曲的歌词。当找不到歌词时,API returns 400 未找到 HTTP 代码,也许我可以在 api 方法调用的某处检查此类错误代码,稍后从视图中检查或类似的?
这是我的视图,如果有可用的互联网连接,只需调用 api,这会切换 sheet 以呈现:
HStack {
Spacer()
Button(action: {
if(!NetworkMonitor.shared.isConnected) {
self.noConnectionAlert.toggle()
} else {
viewModel.loadApiSongData(songName: songName, artistName: artistName)
//self.showingLyricsSheet = true
}
}, label: {
CustomButton(sfSymbolName: "music.note", text: "Search Lyrics!")
})
.alert(isPresented: $noConnectionAlert) {
Alert(title: Text("No internet connection"), message: Text("Oops! It seems you arent connected to the internet. Please connect and try again!"), dismissButton: .default(Text("Got it!")))
}
Spacer()
}
.padding(.top, 20)
.sheet(item: $viewModel.apiResponse) { item in
LyricsView(vm: self.viewModel, songName: songName, artistName: artistName, arrLyrics: item.apiValues)
}
这是我的API调用方法:
class ViewModel : ObservableObject {
@Published var searchedSongs = [SongDetails]() {
didSet {
print(searchedSongs)
}
}
@Published var apiResponse : APIResponse?
func loadApiSongData(songName: String, artistName: String) {
let rawUrl = "https://api.lyrics.ovh/v1/\(artistName)/\(songName)"
let fixedUrl = rawUrl.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)
print("Old url: \(rawUrl)")
print("New url: \(fixedUrl!)")
guard let url = URL(string: fixedUrl!) else {
print("Invalid URL")
return
}
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) { data, response, error in
if let data = data {
if let decodedResponse = try? JSONDecoder().decode(Song.self, from: data) {
// we have good data – go back to the main thread
DispatchQueue.main.async {
// update our UI
print("Found good lyrics")
if(!self.songAlreadySearched(songName: songName)) {
let song = SongDetails(songName: songName, artistName: artistName, lyrics: decodedResponse.lyrics)
self.searchedSongs.append(song)
}
self.apiResponse = APIResponse(apiValues: [decodedResponse.lyrics])
}
// everything is good, so we can exit
return
}
}
print("Fetch failed: \(error?.localizedDescription ?? "Unknown error")")
}.resume()
}
@Published var apiError: APIError? // Added error enum in ViewModel. Use this in your view. Add message string in enum whatever you want to display in UI
URLSession.shared.dataTask(with: request) { data, response, error in
DispatchQueue.main.async {
self.responseHandler(data, response, error, songName, artistName)
}
}.resume()
和处理函数
private func responseHandler(_ data: Data?,
_ response: URLResponse?,
_ error: Error?,
_ songName: String,
_ artistName: String) {
if let error = error {
if error._code == -1009 {
apiError = .offline
} else {
apiError = .sessionError
}
return
}
guard let response = response as? HTTPURLResponse, let data = data else {
apiError = .missingDataError
return
}
guard (200..<300).contains(response.statusCode) else {
switch Status(rawValue: response.statusCode) {
case .requestTimeout:
apiError = .timeoutError
case .internalServerError:
apiError = .internalServerError
case .notFound:
apiError = .notFound
default:
apiError = .requestError
}
return
}
do {
let decodedResponse = try JSONDecoder().decode(Song.self, from: data)
if(!songAlreadySearched(songName: songName)) {
let song = SongDetails(songName: songName, artistName: artistName, lyrics: decodedResponse.lyrics)
searchedSongs.append(song)
}
apiResponse = APIResponse(apiValues: decodedResponse.lyrics)
} catch {
apiError = .parsingError
}
}
枚举错误
enum APIError: Error {
case sessionError
case parsingError
case missingDataError
case requestError
case timeoutError
case offline
case internalServerError
case notFound
case genericError
}
enum Status: Int {
case multipleChoices = 300
case badRequest = 400
case forbidden = 403
case notFound = 404
case requestTimeout = 408
case internalServerError = 500
case notImplemented = 501
case badGateway = 502
case serviceUnavailabe = 503
case gatewayTimeout = 504
}
所以我正在编写一个简单的歌词应用程序,它使用 API 接收歌曲名称和艺术家以及 returns 歌词。如果我发送正确的歌曲名称和艺术家,一切都会按预期进行,但我很难从视图中检测到错误并做出相应的反应,例如在 sheet 被渲染之前显示警报之类的。我的意思是错误,例如用户输入的歌曲或艺术家姓名打错了,这会使 API 无法获取此类歌曲的歌词。当找不到歌词时,API returns 400 未找到 HTTP 代码,也许我可以在 api 方法调用的某处检查此类错误代码,稍后从视图中检查或类似的?
这是我的视图,如果有可用的互联网连接,只需调用 api,这会切换 sheet 以呈现:
HStack {
Spacer()
Button(action: {
if(!NetworkMonitor.shared.isConnected) {
self.noConnectionAlert.toggle()
} else {
viewModel.loadApiSongData(songName: songName, artistName: artistName)
//self.showingLyricsSheet = true
}
}, label: {
CustomButton(sfSymbolName: "music.note", text: "Search Lyrics!")
})
.alert(isPresented: $noConnectionAlert) {
Alert(title: Text("No internet connection"), message: Text("Oops! It seems you arent connected to the internet. Please connect and try again!"), dismissButton: .default(Text("Got it!")))
}
Spacer()
}
.padding(.top, 20)
.sheet(item: $viewModel.apiResponse) { item in
LyricsView(vm: self.viewModel, songName: songName, artistName: artistName, arrLyrics: item.apiValues)
}
这是我的API调用方法:
class ViewModel : ObservableObject {
@Published var searchedSongs = [SongDetails]() {
didSet {
print(searchedSongs)
}
}
@Published var apiResponse : APIResponse?
func loadApiSongData(songName: String, artistName: String) {
let rawUrl = "https://api.lyrics.ovh/v1/\(artistName)/\(songName)"
let fixedUrl = rawUrl.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)
print("Old url: \(rawUrl)")
print("New url: \(fixedUrl!)")
guard let url = URL(string: fixedUrl!) else {
print("Invalid URL")
return
}
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) { data, response, error in
if let data = data {
if let decodedResponse = try? JSONDecoder().decode(Song.self, from: data) {
// we have good data – go back to the main thread
DispatchQueue.main.async {
// update our UI
print("Found good lyrics")
if(!self.songAlreadySearched(songName: songName)) {
let song = SongDetails(songName: songName, artistName: artistName, lyrics: decodedResponse.lyrics)
self.searchedSongs.append(song)
}
self.apiResponse = APIResponse(apiValues: [decodedResponse.lyrics])
}
// everything is good, so we can exit
return
}
}
print("Fetch failed: \(error?.localizedDescription ?? "Unknown error")")
}.resume()
}
@Published var apiError: APIError? // Added error enum in ViewModel. Use this in your view. Add message string in enum whatever you want to display in UI
URLSession.shared.dataTask(with: request) { data, response, error in
DispatchQueue.main.async {
self.responseHandler(data, response, error, songName, artistName)
}
}.resume()
和处理函数
private func responseHandler(_ data: Data?,
_ response: URLResponse?,
_ error: Error?,
_ songName: String,
_ artistName: String) {
if let error = error {
if error._code == -1009 {
apiError = .offline
} else {
apiError = .sessionError
}
return
}
guard let response = response as? HTTPURLResponse, let data = data else {
apiError = .missingDataError
return
}
guard (200..<300).contains(response.statusCode) else {
switch Status(rawValue: response.statusCode) {
case .requestTimeout:
apiError = .timeoutError
case .internalServerError:
apiError = .internalServerError
case .notFound:
apiError = .notFound
default:
apiError = .requestError
}
return
}
do {
let decodedResponse = try JSONDecoder().decode(Song.self, from: data)
if(!songAlreadySearched(songName: songName)) {
let song = SongDetails(songName: songName, artistName: artistName, lyrics: decodedResponse.lyrics)
searchedSongs.append(song)
}
apiResponse = APIResponse(apiValues: decodedResponse.lyrics)
} catch {
apiError = .parsingError
}
}
枚举错误
enum APIError: Error {
case sessionError
case parsingError
case missingDataError
case requestError
case timeoutError
case offline
case internalServerError
case notFound
case genericError
}
enum Status: Int {
case multipleChoices = 300
case badRequest = 400
case forbidden = 403
case notFound = 404
case requestTimeout = 408
case internalServerError = 500
case notImplemented = 501
case badGateway = 502
case serviceUnavailabe = 503
case gatewayTimeout = 504
}