2
\$\begingroup\$

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.
        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?
\$\endgroup\$

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.