使用 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 APIcount 参数来请求特定数量的图像。我对您的代码进行了一些更改,以接收 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())
            ...
        }