1. DownloadWithEscaping 下载数据并且转义返回数据
1.1 实现
struct PostDataModel:Identifiable, Codable {
let userId: Int
let id: Int
let title: String
let body: String
}
/// ViewModel
class DownloadWithEscapingViewModel: ObservableObject{
@Published var posts: [PostDataModel] = []
init() {
getPosts()
}
// post 请求
func getPosts(){
// https://jsonplaceholder.typicode.com/posts
// https://jsonplaceholder.typicode.com/posts/1
guard let url = URL(string: "https://jsonplaceholder.typicode.com/posts") else{ return }
downloadData(formURL: url) { returnedData in
if let data = returnedData{
// 解码模型
guard let newPosts = try? JSONDecoder().decode([PostDataModel].self, from: data) else { return }
DispatchQueue.main.async { [weak self] in
// 添加到数组中
//self?.posts.append(newPost)
self?.posts = newPosts
}
}else{
print("No data returned.")
}
}
}
/// 封装请求接口 formURL: 外部名称 @escaping: 转义闭包
func downloadData(formURL url: URL, completionHandler: @escaping (_ data: Data?) -> ()){
URLSession.shared.dataTask(with: url) { data, response, error in
// 检查有没有数据
//guard let data = data else{
// print("No data.")
// return
//}
// 检查有没有错误
//guard error == nil else{
// print("Error: \(String(describing: error))")
// return
//}
// 判断返回 httpurl 响应
//guard let response = response as? HTTPURLResponse else{
// print("Invalid response.")
// return
//}
// 判断返回状态码
//guard response.statusCode >= 200 && response.statusCode < 300 else{
// print("Status code should be 2xx, but is \(response.statusCode)")
// return
//}
// 打印接收数据
//print("Successfully downloaded data!")
//print(data)
//let jsonData = String(data: data, encoding: .utf8)
//print(jsonData ?? "")
guard let data = data,
error == nil,
let response = response as? HTTPURLResponse,
response.statusCode >= 200 && response.statusCode < 300 else{
print("Error downloading data.")
completionHandler(nil)
return
}
// 返回数据
completionHandler(data)
}.resume()
}
}
/// 下载并且转义
struct DownloadWithEscapingBootcamp: View {
@StateObject var vm = DownloadWithEscapingViewModel()
var body: some View {
List {
ForEach(vm.posts) { post in
VStack(alignment: .leading) {
Text(post.title)
.font(.headline)
Text(post.body)
.foregroundColor(.gray)
}
.frame(maxWidth: .infinity, alignment: .leading)
}
}
}
}
1.2 效果图:
2. DownloadWithCombine 下载数据并且组合使用数据
2.1 实现
import Combine
struct PostModel:Identifiable, Codable {
let userId: Int
let id: Int
let title: String
let body: String
}
// ViewModel
class DownloadWithCombineViewModel: ObservableObject{
@Published var posts:[PostModel] = []
// 是否取消请求
var cancellables = Set<AnyCancellable>()
init() {
getPosts()
}
// 获取数据
func getPosts(){
guard let url = URL(string: "https://jsonplaceholder.typicode.com/posts") else{ return }
// Combine discussion:
/*
// 1. 注册信息,按月订阅一个包裹,订阅后然后发送给公司
// 2. 公司将制做包裹,因此工厂继续制作他们的包裹
// 3. 制作完包裹,然后运送包裹,您可能会在门前收到包裹
// 4. 您将检查箱子,确保箱子没有损坏
// 5. 打开并确保商品无误
// 6. 最终可以使用
// 7. 订阅的包裹,可以随时取消
// 1. 创建发布者
// 2. 后台线程上订阅发布者
// 3. 主线程上接收
// 4. tryMap 检查数据是否完好
// 5. 解码 (解码数据到模型(PostModel))
// 6. sink,进行同步,并将 item 放入项目
// 7. store (随时取消订阅)
*/
URLSession.shared.dataTaskPublisher(for: url)
//.subscribe(on: DispatchQueue.global(qos: .background))
.receive(on: DispatchQueue.main)
.tryMap(handleOutput)
.decode(type: [PostModel].self, decoder: JSONDecoder())
// 不想处理错误信息,简单写法
.replaceError(with: []) // 替代错误
.sink(receiveValue: { [weak self]returnedPosts in
self?.posts = returnedPosts
})
/*.sink { completion in
// 打印信息 -> 包含 URLError
//print("Completion: \(completion)")
switch completion {
case .finished:
print("Finished")
case .failure(let error):
print("There was an error: \(error)")
}
} receiveValue: {[weak self] returnedPosts in
self?.posts = returnedPosts
}*/
.store(in: &cancellables)
}
// 抽取函数
func handleOutput(output: URLSession.DataTaskPublisher.Output) throws -> Data{
guard let response = output.response as? HTTPURLResponse,
response.statusCode >= 200 && response.statusCode < 300 else{
throw URLError(.badServerResponse)
}
return output.data
}
}
// 组合操作
struct DownloadWithCombine: View {
@StateObject var viewModel = DownloadWithCombineViewModel()
var body: some View {
List {
ForEach(viewModel.posts) { post in
VStack(alignment: .leading) {
Text(post.title)
.font(.headline)
Text(post.body)
.foregroundColor(.gray)
}
.frame(maxWidth: .infinity, alignment: .leading)
}
}
}
}
2.2 效果图: