Swift - JSONDecoder & Combine 框架问题
Posted
技术标签:
【中文标题】Swift - JSONDecoder & Combine 框架问题【英文标题】:Swift - JSONDecoder & Combine framework issue 【发布时间】:2021-07-01 20:00:39 【问题描述】:我在视图中有一个搜索栏,我可以在其中进行搜索,搜索将被传递到 REST api,结果将显示在 tableView 上。以下是我的不同课程
型号:
struct MovieResponse: Codable
var totalResults: Int
var response: String
var error: String
var movies: [Movie]
enum ConfigKeys: String, CodingKey
case totalResults
case response = "Response"
case error = "Error"
case movies
init(totalResults: Int, response: String, error: String, movies: [Movie])
self.totalResults = totalResults
self.response = response
self.error = error
self.movies = movies
init(from decoder: Decoder) throws
let values = try decoder.container(keyedBy: CodingKeys.self)
self.totalResults = try values.decodeIfPresent(Int.self, forKey: .totalResults) ?? 0
self.response = try values.decodeIfPresent(String.self, forKey: .response) ?? "False"
self.error = try values.decodeIfPresent(String.self, forKey: .error) ?? ""
self.movies = try values.decodeIfPresent([Movie].self, forKey: .movies) ?? []
extension MovieResponse
struct Movie: Codable, Identifiable
var id = UUID()
var title: String
var year: Int8
var imdbID: String
var type: String
var poster: URL
enum EncodingKeys: String, CodingKey
case title = "Title"
case year = "Year"
case imdmID
case type = "Type"
case poster = "Poster"
视图模型:
final class MovieListViewModel: ObservableObject
@Published var isLoading: Bool = false
@Published var movieObj = MovieResponse(totalResults: 0, response: "False", error: "", movies: [])
var searchTerm: String = ""
private let searchTappedSubject = PassthroughSubject<Void, Error>()
private var disposeBag = Set<AnyCancellable>()
private let service = OMDBService()
init()
searchTappedSubject
.flatMap
self.requestMovies(searchTerm: self.searchTerm)
.handleEvents(receiveSubscription: _ in
DispatchQueue.main.async
self.isLoading = true
,
receiveCompletion: comp in
DispatchQueue.main.async
self.isLoading = false
)
.eraseToAnyPublisher()
.replaceError(with: [])
.receive(on: DispatchQueue.main)
.assign(to: \.movieObj.movies, on: self)
.store(in: &disposeBag)
func onSearchTapped()
searchTappedSubject.send(())
private func requestMovies(searchTerm: String) -> AnyPublisher<[MovieResponse.Movie], Error>
guard let url = URL(string:"\(Constants.HostName)/?s=\(searchTerm)&apikey=\(Constants.APIKey)") else
fatalError("Something is wrong with URL")
return URLSession.shared.dataTaskPublisher(for: url)
.tryMap() element -> Data in
guard let httpResponse = element.response as? HTTPURLResponse,
httpResponse.statusCode == 200 else
throw URLError(.badServerResponse)
return element.data
.mapError $0 as Error
.decode(type: [MovieResponse.Movie].self, decoder: JSONDecoder())
.eraseToAnyPublisher()
最后是视图和搜索栏
struct SearchView: View
@ObservedObject var viewModel = MovieListViewModel()
@State private var searchText = ""
var body: some View
ZStack
VStack
HStack
Text("Search OMDB")
.font(.system(size: 25, weight: .black, design: .rounded))
Spacer()
.padding()
Spacer()
SearchBar(text: $viewModel.searchTerm,
onSearchButtonClicked: viewModel.onSearchTapped)
List(viewModel.movieObj.movies) movie in
Text(verbatim: movie.title)
.onAppear()
print("Got the new data")
struct SearchBar: UIViewRepresentable
@Binding var text: String
var onSearchButtonClicked: (() -> Void)? = nil
class Coordinator: NSObject, UISearchBarDelegate
let control: SearchBar
init(_ control: SearchBar)
self.control = control
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String)
control.text = searchText
func searchBarSearchButtonClicked(_ searchBar: UISearchBar)
control.onSearchButtonClicked?()
func makeCoordinator() -> Coordinator
return Coordinator(self)
func makeUIView(context: UIViewRepresentableContext<SearchBar>) -> UISearchBar
let searchBar = UISearchBar(frame: .zero)
searchBar.delegate = context.coordinator
return searchBar
func updateUIView(_ uiView: UISearchBar, context: UIViewRepresentableContext<SearchBar>)
uiView.text = text
当我运行代码时,REST api 正在返回数据,但我在 Movie
数组中看不到相同的数据,而 List
没有显示任何内容。
编辑: 添加 REST API 返回的示例 json
"Search": [
"Title": "What We Do in the Shadows",
"Year": "2014",
"imdbID": "tt3416742",
"Type": "movie",
"Poster": "https://m.media-amazon.com/images/M/MV5BMjAwNDA5NzEwM15BMl5BanBnXkFtZTgwMTA1MDUyNDE@._V1_SX300.jpg"
,
"Title": "I Know What You Did Last Summer",
"Year": "1997",
"imdbID": "tt0119345",
"Type": "movie",
"Poster": "https://m.media-amazon.com/images/M/MV5BZDI4ODJlNGUtNjFiMy00ODgzLWIzYjgtMzgyZTljZDQ2NGZiXkEyXkFqcGdeQXVyMTQxNzMzNDI@._V1_SX300.jpg"
],
"totalResults": "4365",
"Response": "True"
【问题讨论】:
您使用的是.replaceError(with: [])
,因此您实际上可能看不到错误。您确定在此之前的某个地方没有发生错误吗?
运行时无任何错误发生。我怀疑它是一个解码问题,但无法找到确切的问题。
对——如果是解码错误,我建议您实际捕获该错误并做出响应,而不是仅仅将错误替换为[]
我认为问题在于movieObj 是@Published,而不是电影(应该触发列表重新加载)。 @Published var movieObj = MovieResponse(totalResults: 0, response: "False", error: "", movies: []) 如果你有 @Published var movies = [],并在收到响应后设置它,它应该可以工作.
【参考方案1】:
您的代码几乎没有问题。
-
主要是您尝试解码的类型不正确。数据是一个字典,可以解码为
MovieResponse
,但您正试图解码为[MovieResponse.Movie]
。这将失败。
您可以通过添加轻松找到解码/网络错误
.mapError( (error) -> Error in
print("error -- \(error)")
return error
)
修复此更改
.decode(type: [MovieResponse.Movie].self, decoder: JSONDecoder())
到
.decode(type: MovieResponse.self, decoder: JSONDecoder())
.map(\.movies)
还有一些与编码键不匹配的问题,
对于MovieResponse
-
将拼写错误
ConfigKeys
修复为 CodingKeys
movies
数组应该从 Search
对象解码
totalResults
应该是一个字符串。
对于Movie
-
将
EncodingKeys
更改为CodingKeys
将拼写错误 imdmID
修复为 imdbID
将year
的类型更改为String
或使用自定义解码
这些更改将解决映射问题。
提示:至少在发布问题之前检查您的拼写错误;)
【讨论】:
虽然@Johnykutty 是正确的,但我也建议您在掌握编写解码器的窍门之前稍微作弊。这可能非常令人生畏。首先,我将通过 JSON Formatter 运行 JSON 响应以清理它,然后通过 Quicktype 运行它,这将为您编写一个解码器。解码器会笨重且丑陋,但它会起作用,您可以了解如何使用您关心的数据为自己构建解码器。然后,您可以学习编写更好、更简洁的解码器。 Quicktype 很棒。 我已按照建议执行了所有操作,但仍然无法看到列表。我想我放弃了Combine
和PassthroughSubject
的想法
我尝试使用您的代码,但结果是我必须按 Enter 才能显示列表。
Quicktype 是一个开始,而不是结束。根据您用于创建解码器的返回数据,您可以获得一些奇怪且不必要的变量。此外,@Johnykutty 是对的,您设置搜索器的方式不会导致列表自动出现。它会等到您按 Enter 键开始搜索。否则,输入的每个字符都会导致重新搜索数据库。有办法解决这个问题,但这超出了你现在想做的事情。以上是关于Swift - JSONDecoder & Combine 框架问题的主要内容,如果未能解决你的问题,请参考以下文章
JSONDecoder() 仅在 Swift 中处理 Null 值
如何在 swift 中使用 JSONDecoder 输入调整?
Swift 5:如何从 JSONDecoder().decode 获取数据?