Automatically try reloading failed images on network changes (#3170)

* Add the networkMonitor to the NSE and delete the old MockMediaLoader

* Generate a MediaLoaderMock through AutoMockable and use it in the MediaProviderTests

* Implement an ImageProvider mechanism that automatically tries reloading images when connectivity is established again and use it for LoadableImages

* Merge the ImageProvider protocol back into the MediaProvider

* Address PR review comments

* Provide a default sdk client request configuration

* Address tasks not automatically cancelling themselves when views get deallocated
This commit is contained in:
Stefan Ceriu
2024-08-15 18:20:19 +03:00
committed by GitHub
parent cf35056c78
commit f89c3e54b4
92 changed files with 618 additions and 332 deletions
@@ -14,17 +14,21 @@
// limitations under the License.
//
import Combine
import Kingfisher
import UIKit
struct MediaProvider: MediaProviderProtocol {
private let mediaLoader: MediaLoaderProtocol
private let imageCache: Kingfisher.ImageCache
private let networkMonitor: NetworkMonitorProtocol?
init(mediaLoader: MediaLoaderProtocol,
imageCache: Kingfisher.ImageCache) {
imageCache: Kingfisher.ImageCache,
networkMonitor: NetworkMonitorProtocol?) {
self.mediaLoader = mediaLoader
self.imageCache = imageCache
self.networkMonitor = networkMonitor
}
// MARK: Images
@@ -71,6 +75,45 @@ struct MediaProvider: MediaProviderProtocol {
}
}
func loadImageRetryingOnReconnection(_ source: MediaSourceProxy, size: CGSize?) -> Task<UIImage, any Error> {
guard let networkMonitor else {
fatalError("This method shouldn't be invoked without a NetworkMonitor set.")
}
return Task {
if case let .success(image) = await loadImageFromSource(source, size: size) {
return image
}
guard !Task.isCancelled else {
throw MediaProviderError.cancelled
}
for await reachability in networkMonitor.reachabilityPublisher.values {
guard !Task.isCancelled else {
throw MediaProviderError.cancelled
}
guard reachability == .reachable else {
continue
}
switch await loadImageFromSource(source, size: size) {
case .success(let image):
return image
case .failure:
// If it fails after a retry with the network available
// then something else must be wrong. Bail out.
if reachability == .reachable {
throw MediaProviderError.cancelled
}
}
}
throw MediaProviderError.cancelled
}
}
func loadImageDataFromSource(_ source: MediaSourceProxy) async -> Result<Data, MediaProviderError> {
do {
let imageData = try await mediaLoader.loadMediaContentForSource(source)