Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create URLDataCache and DataDownloader #164

Merged
merged 5 commits into from
Aug 6, 2021

Conversation

anthony-lipscomb-dev
Copy link
Contributor

@anthony-lipscomb-dev anthony-lipscomb-dev commented Aug 2, 2021

This pull request includes (pick all that apply):

  • Bugfixes
  • New features
  • Breaking changes
  • Documentation updates
  • Unit tests
  • Other

Summary

The ImageDownloader and Cache works great but only supports images that are supported by UIImage/NSImage for the OS version of the device. If the Image doesn't support the data type, the data is lost and not cached, and we are unable to fully utilize the cache if we support other image types from our application.

URLDataCache and DataDownloader removes the Image requirement from downloading the data needed while maintaining the functionality of ImageDownloader and ImageCache.

Implementation

In order to reduce duplicated code, most of the existing code from URLImageCache.swift and AutoPurgingURLImageCache.swift and has been replaced with NSData in place of UIImage and NSImage. This allows for more flexibility in the data types being returned from dynamic URL types that we might not always have control over.

NSData was chosen over Data in order to maintain the use of NSCache which requires the use of a class [NSData], instead of a struct [Data]. Since these are interoperable, I felt this wouldn't be an issue.

Test Plan

  • Ensure Images are still downloaded, cached, and retrieved properly. This can be done using the tests or in your application. I have used Proxyman to determine that multiple requests were not being made by the application to request subsequent Images from the web. In prior versions these requests would continue to be made because once UIImage/NSImage is instantiated from Data, the cached data is lost.
  • [New functionality] Ensure unsupported image types from the web are being stored properly. This can be done using the tests or in your application. I have used Proxyman to determine that multiple requests were not being made by the application to request subsequent Images from the web.

@discardableResult
public func downloadData(for request: URLRequest, completion: @escaping CompletionHandler) -> SessionTaskProxyType? {
var proxy: SessionTaskProxyType?
let completionQueue = self.completionQueue ?? .current ?? .main
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So we could pass in a background queue to download data. But if left nil it'll default to current or main?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's how it reads to me. Do we want to put this in the init?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We wouldn't want to put this in init.
We might want to init on a different thread than we run the downloader on. It seems more of a convenience if you know you're already going to be on the thread you want when calling downloadData

strongSelf.sessionProxyMap[cacheIdentifier] = nil
strongSelf.completionHandlerMap[cacheIdentifier]?.forEach(execute)
strongSelf.completionHandlerMap[cacheIdentifier] = nil
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please explain the need for intentional retain cycles a bit more?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bconway99 If you look at the comment above that

// Strongly capture self within the completion handler to ensure 
// DataDownloader is persisted long enough to respond

Since there are multiple asynchronous elements in this method we want to make sure we set the data we need before the objects are released


waitForExpectations(timeout: 5)
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great, I'd just recommend adding the "GIVEN, WHEN, THEN" statements to the above tests as well.

Copy link
Contributor

@bconway99 bconway99 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking great just a couple of small comments.

return _data(for: request)
}

private func _data(for request: URLRequest) -> NSData? {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wondering if the private functions should be removed since they both are called from public ones without any extra logic. Is there some other reason for breaking them up?


// Strongly capture self within the completion handler to ensure
// DataDownloader is persisted long enough to respond
let strongSelf = self
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

with the above guard statement (line 79) defined as it is self here is non-optional unless I'm misreading the scopes

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agreed that's what it seems like to me too

Copy link
Contributor

@plflanagan plflanagan Aug 5, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still don't think this is needed with the guard let self = self above; doesn't that accomplish what this does?

Copy link
Contributor

@plflanagan plflanagan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work. I've left a few questions/comments throughout 👍

@discardableResult
public func downloadData(for request: URLRequest, completion: @escaping CompletionHandler) -> SessionTaskProxyType? {
var proxy: SessionTaskProxyType?
let completionQueue = self.completionQueue ?? .current ?? .main
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's how it reads to me. Do we want to put this in the init?


// Strongly capture self within the completion handler to ensure
// DataDownloader is persisted long enough to respond
let strongSelf = self
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agreed that's what it seems like to me too


// Strongly capture self within the completion handler to ensure
// DataDownloader is persisted long enough to respond
let strongSelf = self
Copy link
Contributor

@plflanagan plflanagan Aug 5, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still don't think this is needed with the guard let self = self above; doesn't that accomplish what this does?

@anthony-lipscomb-dev anthony-lipscomb-dev merged commit 44611d6 into main Aug 6, 2021
@anthony-lipscomb-dev anthony-lipscomb-dev deleted the task/create-data-downloader branch August 6, 2021 00:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants