I have URLSession shared instance that fetches data. It returns a generic type. Everything works fine when I'm fetching data from a server and displaying it. But when I fetch an image I need to return the Generic Type as the data. How can I return the generic type/data so that it can be used to create the image in the imageView?
UIImageView:
extension UIImageView {
func loadImage(with url: URL) {
if let cachedImage = imageCache.object(forKey: url.absoluteString as AnyObject) as? UIImage {
self.image = cachedImage
return
}
URLSessionManager.fetchData(with: url, isImageFetch: true) { (result: Result<Data, SessionDataTaskError>) in
switch result {
case .failure(let error):
print(error)
case .success(let data):
guard let downloadedImage = UIImage(data: data) else {
print("image data is nil")
return
}
DispatchQueue.main.async{ [weak self] in
imageCache.setObject(downloadedImage, forKey: url.absoluteString as AnyObject)
self?.image = downloadedImage
}
}
}
}
}
URLSessionManager:
class URLSessionManager {
static let shared = URLSessionManager()
static func fetchData<T: Decodable>(with url: URL, isImageFetch: Bool = false, completion: @escaping (Result<T, SessionDataTaskError>)->Void) { // SessionDataTaskError is just an Enum
URLSession.shared.dataTask(with: url) { (data, response, error) in
if let error = error {
completion(.failure(SessionDataTaskError.failedIssue(error)))
return
}
guard let data = data else {
completion(.failure(SessionDataTaskError.dataIsNil))
return
}
if isImageFetch {
// *** How can I return the generic type/data so that it can be used to create the image in the imageView? ***
return
}
do {
let genericType = try JSONDecoder().decode(T.self, from: data)
completion(.success(genericType))
} catch {
completion(.failure(SessionDataTaskError.catchIssue(error)))
guard let err = error as? DecodingError else {
debugPrint("decodingError-:", error)
return
}
switch err {
case .typeMismatch(let key, let value):
print("decodingError-Mismatch: \(key), value \(value) and ERROR: \(error.localizedDescription)")
case .valueNotFound(let key, let value):
print("decodingError-ValueNotFound: \(key), value \(value) and ERROR: \(error.localizedDescription)")
case .keyNotFound(let key, let value):
print("decodingError-KeyNotFound(: \(key), value \(value) and ERROR: \(error.localizedDescription)")
case .dataCorrupted(let key):
print("decodingError-DataCorrupted: \(key), and ERROR: \(error.localizedDescription)")
default:
print("decodingError-UnknownError: \(error.localizedDescription)")
}
}
}.resume()
}
}
CodePudding user response:
The fetchData<T: Decodable>
is for performing JSONDecoder
on the body of the response. But an image is not JSON, so it does not make sense to call that method when the response is actually an image. Generally, you would have a separate implementation that returns a Data
or a UIImage
.
So I would have a non-generic fetchData
that retrieves a Data
upon success. Then I would have a generic fetchObject
that calls fetchData
decodes the generic with JSONDecoder
. And I would have a non-generic fetchImage
that also calls fetchData
, but which returns a UIImage
.
As an aside, I would be very careful with loadImage(with:)
. You do not just want to just update the UIImageView
, because there is a risk (especially with collection and table views) where cells might be reused in the intervening time, which can cause very strange results where the wrong image may appear if you are scrolling quickly through the collection/table view). See https://stackoverflow.com/a/45183939/1271826 for an example of the use of “associated objects” to keep track of pending request, if any. This pattern also avoids it from getting backlogged if the user scrolls quickly.