Skip to content

Commit

Permalink
Allow passing a custom completion queue to ImageDownloader (#162)
Browse files Browse the repository at this point in the history
### Summary
Allow passing a custom operation queue to `ImageDownloader` to be used when calling the completion callback.

### Implementation
- Add `completionQueue` parameter to initializer (defaults to `nil`)
  - If no value is passed, `ImageDownloader` will behave as before, defaulting to the current operation queue for completion, or main if there is no current queue.
  - When a queue is specified, it will be used instead.
  • Loading branch information
eneko authored Jul 1, 2021
1 parent 805e7f4 commit fdf1e5c
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 9 deletions.
24 changes: 15 additions & 9 deletions Sources/Conduit/Networking/Images/ImageDownloader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -66,17 +66,22 @@ public final class ImageDownloader: ImageDownloaderType {
private let sessionClient: URLSessionClientType
private var sessionProxyMap: [String: SessionTaskProxyType] = [:]
private var completionHandlerMap: [String: [CompletionHandler]] = [:]
private let completionQueue: OperationQueue?
private let serialQueue = DispatchQueue(
label: "com.mindbodyonline.Conduit.ImageDownloader-\(UUID().uuidString)"
)

/// Initializes a new ImageDownloader
/// - Parameters:
/// - cache: The image cache in which to store downloaded images
/// - sessionClient: The URLSessionClient to be used to download images
public init(cache: URLImageCache, sessionClient: URLSessionClientType = URLSessionClient()) {
/// - cache: The image cache in which to store downloaded images
/// - sessionClient: The URLSessionClient to be used to download images
/// - completionQueue: An optional operation queue for completion callback
public init(cache: URLImageCache,
sessionClient: URLSessionClientType = URLSessionClient(),
completionQueue: OperationQueue? = nil) {
self.cache = cache
self.sessionClient = sessionClient
self.completionQueue = completionQueue
}

/// Downloads an image or retrieves it from the cache if previously downloaded.
Expand Down Expand Up @@ -116,18 +121,19 @@ public final class ImageDownloader: ImageDownloaderType {

// Strongly capture self within the completion handler to ensure
// ImageDownloader is persisted long enough to respond
let strongSelf = self
proxy = self.sessionClient.begin(request: request) { data, response, error in
var image: Image?
if let data = data {
image = Image(data: data)
}

if let image = image {
self.cache.cache(image: image, for: request)
strongSelf.cache.cache(image: image, for: request)
}

let response = Response(image: image, error: error, urlResponse: response, isFromCache: false)
let queue = OperationQueue.current ?? OperationQueue.main
let queue = strongSelf.completionQueue ?? .current ?? .main

func execute(handler: @escaping CompletionHandler) {
queue.addOperation {
Expand All @@ -136,10 +142,10 @@ public final class ImageDownloader: ImageDownloaderType {
}

// Intentional retain cycle that releases immediately after execution
self.serialQueue.async {
self.sessionProxyMap[cacheIdentifier] = nil
self.completionHandlerMap[cacheIdentifier]?.forEach(execute)
self.completionHandlerMap[cacheIdentifier] = nil
strongSelf.serialQueue.async {
strongSelf.sessionProxyMap[cacheIdentifier] = nil
strongSelf.completionHandlerMap[cacheIdentifier]?.forEach(execute)
strongSelf.completionHandlerMap[cacheIdentifier] = nil
}
}

Expand Down
40 changes: 40 additions & 0 deletions Tests/ConduitTests/Networking/Images/ImageDownloaderTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -114,4 +114,44 @@ class ImageDownloaderTests: XCTestCase {
waitForExpectations(timeout: 5)
}

func testMainOperationQueue() throws {
// GIVEN a main operation queue
let expectedQueue = OperationQueue.main

// AND a configured Image Downloader instance
let imageDownloadedExpectation = expectation(description: "image downloaded")
let sut = ImageDownloader(cache: AutoPurgingURLImageCache())
let url = try URL(absoluteString: "https://httpbin.org/image/jpeg")
let imageRequest = URLRequest(url: url)

// WHEN downloading an image
sut.downloadImage(for: imageRequest) { _ in
// THEN the completion handler is called in the expected queue
XCTAssertEqual(OperationQueue.current, expectedQueue)
imageDownloadedExpectation.fulfill()
}

waitForExpectations(timeout: 5)
}

func testCustomOperationQueue() throws {
// GIVEN a custom operation queue
let customQueue = OperationQueue()

// AND a configured Image Downloader instance with our custom completion queue
let imageDownloadedExpectation = expectation(description: "image downloaded")
let sut = ImageDownloader(cache: AutoPurgingURLImageCache(), completionQueue: customQueue)
let url = try URL(absoluteString: "https://httpbin.org/image/jpeg")
let imageRequest = URLRequest(url: url)

// WHEN downloading an image
sut.downloadImage(for: imageRequest) { _ in
// THEN the completion handler is called in our custom queue
XCTAssertEqual(OperationQueue.current, customQueue)
imageDownloadedExpectation.fulfill()
}

waitForExpectations(timeout: 5)
}

}

0 comments on commit fdf1e5c

Please sign in to comment.