This is an image representation of my code below:

enter image description here

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.
        // 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"))


        guard let image = determineImage(photoIdentifier: photoIdentifier) else {
            loadableImages.append(WeakObjectWrapper(obj: loadableImage))


        loadableImage.show(image: image)

    func received(image: UIImage, forPhotoIdentifier: Int64) {

        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?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.