Skip to content

Commit

Permalink
Add ImageRequest (#23844)
Browse files Browse the repository at this point in the history
  • Loading branch information
kean authored Nov 22, 2024
1 parent 1d8eeaf commit 909c645
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 47 deletions.
4 changes: 2 additions & 2 deletions WordPress/Classes/Services/MediaImageService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -272,8 +272,8 @@ final class MediaImageService {
// MARK: - Networking

private func data(for info: RemoteImageInfo, isCached: Bool) async throws -> Data {
let options = ImageRequestOptions(isDiskCacheEnabled: isCached)
return try await downloader.data(from: info.imageURL, host: info.host, options: options)
let request = ImageRequest(url: info.imageURL, host: info.host, options: ImageRequestOptions(isDiskCacheEnabled: isCached))
return try await downloader.data(for: request)
}

private struct RemoteImageInfo {
Expand Down
72 changes: 27 additions & 45 deletions WordPress/Classes/Utility/Media/ImageDownloader.swift
Original file line number Diff line number Diff line change
@@ -1,19 +1,5 @@
import UIKit

struct ImageRequestOptions {
/// Resize the thumbnail to the given size. By default, `nil`.
///
/// - warning: The size is in pixels.
var size: CGSize?

/// If enabled, uses ``MemoryCache`` for caching decompressed images.
var isMemoryCacheEnabled = true

/// If enabled, uses `URLSession` preconfigured with a custom `URLCache`
/// with a relatively high disk capacity. By default, `true`.
var isDiskCacheEnabled = true
}

/// The system that downloads and caches images, and prepares them for display.
actor ImageDownloader {
static let shared = ImageDownloader()
Expand All @@ -38,48 +24,44 @@ actor ImageDownloader {
self.cache = cache
}

// MARK: - Images (URL)

/// Downloads image for the given `URL`.
func image(from url: URL, options: ImageRequestOptions = .init()) async throws -> UIImage {
var request = URLRequest(url: url)
request.addValue("image/*", forHTTPHeaderField: "Accept")
return try await image(from: request, options: options)
func image(from url: URL, host: MediaHost? = nil, options: ImageRequestOptions = .init()) async throws -> UIImage {
try await image(for: ImageRequest(url: url, host: host, options: options))
}

/// Downloads image for the given `URLRequest`.
func image(from request: URLRequest, options: ImageRequestOptions = .init()) async throws -> UIImage {
let key = makeKey(for: request.url, size: options.size)
func image(for request: ImageRequest) async throws -> UIImage {
let options = request.options
let key = makeKey(for: request.source.url, size: options.size)
if options.isMemoryCacheEnabled, let image = cache[key] {
return image
}
let data = try await data(for: request, options: options)
let data = try await data(for: request)
let image = try await ImageDecoder.makeImage(from: data, size: options.size)
if options.isMemoryCacheEnabled {
cache[key] = image
}
return image
}

// MARK: - Images (Blog)

/// Returns image for the given URL authenticated for the given host.
func image(from imageURL: URL, host: MediaHost, options: ImageRequestOptions = .init()) async throws -> UIImage {
let request = try await authenticatedRequest(for: imageURL, host: host)
return try await image(from: request, options: options)
}

/// Returns data for the given URL authenticated for the given host.
func data(from imageURL: URL, host: MediaHost, options: ImageRequestOptions = .init()) async throws -> Data {
let request = try await authenticatedRequest(for: imageURL, host: host)
return try await data(for: request, options: options)
func data(for request: ImageRequest) async throws -> Data {
let urlRequest = try await makeURLRequest(for: request)
return try await _data(for: urlRequest, options: request.options)
}

private func authenticatedRequest(for imageURL: URL, host: MediaHost) async throws -> URLRequest {
var request = try await MediaRequestAuthenticator()
.authenticatedRequest(for: imageURL, host: host)
request.setValue("image/*", forHTTPHeaderField: "Accept")
return request
private func makeURLRequest(for request: ImageRequest) async throws -> URLRequest {
switch request.source {
case .url(let url, let host):
var request: URLRequest
if let host {
request = try await MediaRequestAuthenticator()
.authenticatedRequest(for: url, host: host)
} else {
request = URLRequest(url: url)
}
request.addValue("image/*", forHTTPHeaderField: "Accept")
return request
case .urlRequest(let urlRequest):
return urlRequest
}
}

// MARK: - Caching
Expand Down Expand Up @@ -115,8 +97,8 @@ actor ImageDownloader {

// MARK: - Networking

private func data(for request: URLRequest, options: ImageRequestOptions) async throws -> Data {
let requestKey = request.urlRequest?.url?.absoluteString ?? ""
private func _data(for request: URLRequest, options: ImageRequestOptions) async throws -> Data {
let requestKey = request.url?.absoluteString ?? ""
let task = tasks[requestKey] ?? ImageDataTask(key: requestKey, Task {
try await self._data(for: request, options: options, key: requestKey)
})
Expand Down Expand Up @@ -198,7 +180,7 @@ extension ImageDownloader {
nonisolated func downloadImage(for request: URLRequest, completion: @escaping (UIImage?, Error?) -> Void) -> ImageDownloaderTask {
let task = Task {
do {
let image = try await self.image(from: request, options: .init())
let image = try await self.image(for: ImageRequest(urlRequest: request))
completion(image, nil)
} catch {
completion(nil, error)
Expand Down
40 changes: 40 additions & 0 deletions WordPress/Classes/Utility/Media/ImageRequest.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import UIKit

final class ImageRequest {
enum Source {
case url(URL, MediaHost?)
case urlRequest(URLRequest)

var url: URL? {
switch self {
case .url(let url, _): url
case .urlRequest(let request): request.url
}
}
}

let source: Source
let options: ImageRequestOptions

init(url: URL, host: MediaHost? = nil, options: ImageRequestOptions = .init()) {
self.source = .url(url, host)
self.options = options
}

init(urlRequest: URLRequest, options: ImageRequestOptions = .init()) {
self.source = .urlRequest(urlRequest)
self.options = options
}
}

struct ImageRequestOptions {
/// Resize the thumbnail to the given size. By default, `nil`.
var size: CGSize?

/// If enabled, uses ``MemoryCache`` for caching decompressed images.
var isMemoryCacheEnabled = true

/// If enabled, uses `URLSession` preconfigured with a custom `URLCache`
/// with a relatively high disk capacity. By default, `true`.
var isDiskCacheEnabled = true
}

0 comments on commit 909c645

Please sign in to comment.