使用 Swift 组合多次重复网络请求
Repeat Network Request Multiple Times With Swift Combine
我正在尝试使用 Unsplash API 获取五张随机照片 url 的组合管道,然后从每张 url 下载一张照片。我 运行 遇到的问题是图像大多相同。有时(我不知道为什么)其中一张图片会与其他四张不同。
如有任何帮助,我们将不胜感激。除了 UnSplash Api 键之外,我已经包含了所有必要的代码。
static func fetchRandomPhotos() -> AnyPublisher<UIImage, Error>{
let string = createURL(path: "/photos/random")
return [1,2,3,4,5]
.publisher
.flatMap{ i -> AnyPublisher<UnsplashImageResults, Error> in
return self.downloadAndDecode(string, type: UnsplashImageResults.self)
}
.flatMap{ result -> AnyPublisher<UIImage, Error> in
print(result.urls.thumb)
let url = URL(string: result.urls.thumb)!
return URLSession.shared.dataTaskPublisher(for: url)
.map { UIImage(data: [=11=].data)! }
.mapError{_ in NetworkError.invalidURL}
.eraseToAnyPublisher()
}
.receive(on: RunLoop.main)
.eraseToAnyPublisher()
}
struct UnsplashURLs: Decodable{
let full: String
let regular: String
let small: String
let thumb: String
}
struct UnsplashImageResults: Decodable{
let urls: UnsplashURLs
}
enum NetworkError: LocalizedError{
case invalidURL
var errorDescription: String? {
switch self {
case .invalidURL:
return "The url is invalid"
}
}
}
static private func createURL(path: String)-> String{
var components = URLComponents()
components.scheme = "https"
components.host = "api.unsplash.com"
components.path = path
components.queryItems = [
URLQueryItem(name: "client_id", value: "YOUR API KEY HERE")
]
return components.string!
}
static private func downloadAndDecode<T:Decodable>(_ urlString: String, type: T.Type) -> AnyPublisher<T, Error>{
guard let url = URL(string: urlString) else{
return Fail(error: NetworkError.invalidURL).eraseToAnyPublisher()
}
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
}
.decode(type: T.self, decoder: JSONDecoder())
.receive(on: RunLoop.main)
.eraseToAnyPublisher()
}
class UnsplashImagesViewModel: ObservableObject{
var subscriptions = Set<AnyCancellable>()
@Published var images = [UIImage]()
init(){
UnsplashAPI.fetchRandomPhotos()
.sink { (_) in
} receiveValue: { image in
self.images.append(image)
}.store(in: &subscriptions)
}
}
struct UnsplashImagesGrid: View {
@StateObject private var model = UnsplashImagesViewModel()
var body: some View {
List(model.images, id: \.self){ image in
Image(uiImage: image)
}
}
}
Unsplash returns 同一张图片,如果您同时发送多个请求。您可以通过在请求之间引入延迟来测试此行为。
无论如何,您应该使用 Unsplash random API 的 count
参数来请求特定数量的图像。我对您的代码进行了一些更改,以接收 UIImage
的数组。我希望这能让您了解如何进一步改进它。
static func fetchRandomPhotos() -> AnyPublisher<[UIImage], Error>{
let string = createURL(path: "/photos/random")
return Just(())
.print()
.flatMap{ i -> AnyPublisher<[UnsplashImageResults], Error> in
return self.downloadAndDecode(string, type: UnsplashImageResults.self)
}
.flatMap{ results -> AnyPublisher<[UIImage], Error> in
let images = results.map { result -> AnyPublisher<UIImage, Error> in
let url = URL(string: result.urls.thumb)!
return URLSession.shared.dataTaskPublisher(for: url)
.map { UIImage(data: [=10=].data)! }
.mapError{_ in NetworkError.invalidURL}
.eraseToAnyPublisher()
}
return Publishers.MergeMany(images)
.collect()
.eraseToAnyPublisher()
}
.receive(on: RunLoop.main)
.eraseToAnyPublisher()
}
static private func createURL(path: String)-> String{
...
components.queryItems = [
URLQueryItem(name: "client_id", value: "ID"),
URLQueryItem(name: "count", value: "5")
]
...
}
static private func downloadAndDecode<T:Decodable>(_ urlString: String, type: T.Type) -> AnyPublisher<[T], Error>{
...
.decode(type: [T].self, decoder: JSONDecoder())
...
}
我正在尝试使用 Unsplash API 获取五张随机照片 url 的组合管道,然后从每张 url 下载一张照片。我 运行 遇到的问题是图像大多相同。有时(我不知道为什么)其中一张图片会与其他四张不同。
如有任何帮助,我们将不胜感激。除了 UnSplash Api 键之外,我已经包含了所有必要的代码。
static func fetchRandomPhotos() -> AnyPublisher<UIImage, Error>{
let string = createURL(path: "/photos/random")
return [1,2,3,4,5]
.publisher
.flatMap{ i -> AnyPublisher<UnsplashImageResults, Error> in
return self.downloadAndDecode(string, type: UnsplashImageResults.self)
}
.flatMap{ result -> AnyPublisher<UIImage, Error> in
print(result.urls.thumb)
let url = URL(string: result.urls.thumb)!
return URLSession.shared.dataTaskPublisher(for: url)
.map { UIImage(data: [=11=].data)! }
.mapError{_ in NetworkError.invalidURL}
.eraseToAnyPublisher()
}
.receive(on: RunLoop.main)
.eraseToAnyPublisher()
}
struct UnsplashURLs: Decodable{
let full: String
let regular: String
let small: String
let thumb: String
}
struct UnsplashImageResults: Decodable{
let urls: UnsplashURLs
}
enum NetworkError: LocalizedError{
case invalidURL
var errorDescription: String? {
switch self {
case .invalidURL:
return "The url is invalid"
}
}
}
static private func createURL(path: String)-> String{
var components = URLComponents()
components.scheme = "https"
components.host = "api.unsplash.com"
components.path = path
components.queryItems = [
URLQueryItem(name: "client_id", value: "YOUR API KEY HERE")
]
return components.string!
}
static private func downloadAndDecode<T:Decodable>(_ urlString: String, type: T.Type) -> AnyPublisher<T, Error>{
guard let url = URL(string: urlString) else{
return Fail(error: NetworkError.invalidURL).eraseToAnyPublisher()
}
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
}
.decode(type: T.self, decoder: JSONDecoder())
.receive(on: RunLoop.main)
.eraseToAnyPublisher()
}
class UnsplashImagesViewModel: ObservableObject{
var subscriptions = Set<AnyCancellable>()
@Published var images = [UIImage]()
init(){
UnsplashAPI.fetchRandomPhotos()
.sink { (_) in
} receiveValue: { image in
self.images.append(image)
}.store(in: &subscriptions)
}
}
struct UnsplashImagesGrid: View {
@StateObject private var model = UnsplashImagesViewModel()
var body: some View {
List(model.images, id: \.self){ image in
Image(uiImage: image)
}
}
}
Unsplash returns 同一张图片,如果您同时发送多个请求。您可以通过在请求之间引入延迟来测试此行为。
无论如何,您应该使用 Unsplash random API 的 count
参数来请求特定数量的图像。我对您的代码进行了一些更改,以接收 UIImage
的数组。我希望这能让您了解如何进一步改进它。
static func fetchRandomPhotos() -> AnyPublisher<[UIImage], Error>{
let string = createURL(path: "/photos/random")
return Just(())
.print()
.flatMap{ i -> AnyPublisher<[UnsplashImageResults], Error> in
return self.downloadAndDecode(string, type: UnsplashImageResults.self)
}
.flatMap{ results -> AnyPublisher<[UIImage], Error> in
let images = results.map { result -> AnyPublisher<UIImage, Error> in
let url = URL(string: result.urls.thumb)!
return URLSession.shared.dataTaskPublisher(for: url)
.map { UIImage(data: [=10=].data)! }
.mapError{_ in NetworkError.invalidURL}
.eraseToAnyPublisher()
}
return Publishers.MergeMany(images)
.collect()
.eraseToAnyPublisher()
}
.receive(on: RunLoop.main)
.eraseToAnyPublisher()
}
static private func createURL(path: String)-> String{
...
components.queryItems = [
URLQueryItem(name: "client_id", value: "ID"),
URLQueryItem(name: "count", value: "5")
]
...
}
static private func downloadAndDecode<T:Decodable>(_ urlString: String, type: T.Type) -> AnyPublisher<[T], Error>{
...
.decode(type: [T].self, decoder: JSONDecoder())
...
}