This is an image representation of my code below:
This is what they do:
CellWithImageX: Subclasses of UITableViewCell
. They have an image and an loading indicator. Only 1 can be shown at the time.
ImageRequester: Downloads the images and gives ImageCache
a callback when it's done.
FileExplorer: Saves the images.
ImageCache: Manages everything. Since I am most concerned about the current design, I think the code for the ImageCache
is only relevant:
// Used to weakly hold the CellWithImageX image classes. (Is this bad?)
open class WeakObjectWrapper<T: AnyObject> {
public weak var obj: T?
public init(obj: T) {
self.obj = obj
}
}
class ImageCache {
// I can not understand how it can work if it isn't a singleton.
// From every cell, I can easily pass the reference without to much hassle.
// I am not sure for an singleton alternative.
static let sharedInstance = ImageCache()
// TODO: Maybe remove some elements from time to time?
// Yes, this is an array of the images in the table view cell...
private var loadableImages: [WeakObjectWrapper<LoadableImage>] = []
private init() {}
func handleImage(photoIdentifier: Int64, loadableImage: LoadableImage) {
// Bit concenered about a race condition.
// I read about DispatchQueues and Semaphores, can't really
// decide what would be the best way.
assert(Thread.isMainThread)
// LoadableImage has an identifier which we can use to later on match the downloaded image.
loadableImage.identifier = photoIdentifier
guard photoIdentifier != 0 else {
loadableImage.show(image: #imageLiteral(resourceName: "anonymous"))
return
}
guard let image = determineImage(photoIdentifier: photoIdentifier) else {
loadableImages.append(WeakObjectWrapper(obj: loadableImage))
loadableImage.showIndicator()
return
}
loadableImage.show(image: image)
}
func received(image: UIImage, forPhotoIdentifier: Int64) {
assert(Thread.isMainThread)
FileExplorer.sharedInstance.save(image: image.pngData()!, photoIdentifier: forPhotoIdentifier) // This is validated that this works, we can safely force unwrap.
for loadableImage in loadableImages.filter({ $0.obj?.identifier == forPhotoIdentifier }) {
// While iterating it may occur that the reference is gone.
loadableImage.obj?.show(image: image)
}
}
private func determineImage(photoIdentifier: Int64) -> UIImage? {
guard let image = FileExplorer.sharedInstance.determineImage(photoIdentifier: photoIdentifier) else {
ImageRequester.sharedInstance.download(photoIdentifier: photoIdentifier)
return nil
}
return image
}
}
In my cellForRowAt
method in my tableView
, I am calling the ImageCache
handleImage method on my cell.loadableImage
property.
My concerns:
- I am using a few singletons: ImageRequester, ImageCache and FileExplorer. I am not sure if this is the best approach, but I can not imagine a better way because this way, all my cells can easily register themselfs.
- I have a array of weak objects which references to the loadable images inside of a tableViewCell. Isn't there a better way?
- I am asserting I am on the main thread. If I am on multiple threads, race conditions may occur while I loop through the array. I looked into DispatchQueues and Semaphores. I just didn't found the right way to do it, and what the best way would be in my case. Does someone has a suggestion about what way would be a good way in my case?