如何从视图中检测 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
}