2

I Need to load a raw image from a resource and convert it to a UIImage that will be stored later in an image cache.

Every image has an identifier which is used as key in the cache.

When I try to solve that problem with generics, I get this strange error when trying to return a generic object in a completion.

Errorcode: Cannot invoke value of type '(T) -> ()' with argument list '(FrontImage)'

Below is a sample code to reproduce the error.

import UIKit

protocol CacheableImage {
    static func getIdentifier() -> String
}

struct Image {
    var imageData: Data = Data()
}

class BaseUIImage: CacheableImage {
    class func getIdentifier() -> String {
        return ""
    }

    var image: UIImage?

    required init(){}
}

class FrontImage: BaseUIImage {
    override class func getIdentifier() -> String {
        return "FrontImage"
    }
}

class BackImage: BaseUIImage {
    override class func getIdentifier() -> String {
        return "BackImage"
    }
}

class ImageLoader<T: BaseUIImage> {
    func loadImage(forId id: UUID, completion: ((T) -> ())?) {

        let type = getType(forId: id)

        switch type {
        case .frontImage:
            let image = FileSystemResourceLoader.loadResource(forId: id)
            let frontUIImage: FrontImage  = ImageConverter.convertToUIImage(image: image)
            completion(frontUIImage)
        case .backImage:
            let image = FileSystemResourceLoader.loadResource(forId: id)
            let backUIImage: BackImage  = ImageConverter.convertToUIImage(image: image)
            completion(backUIImage)
        }
    }

    func getType(forId id: UUID) -> ImageType {
        return .frontImage // simplified return value
    }
}

class ImageConverter {
    static func convertToUIImage<T: BaseUIImage>(image: Image) -> T {
        return T() // simplified return value
    }
}


class FileSystemResourceLoader {
    static func loadResource(forId id: UUID) -> Image {
        return Image()
    }
}

enum ImageType {
    case frontImage
    case backImage
}
Paulw11
  • 108,386
  • 14
  • 159
  • 186
Niklas
  • 21
  • 1
  • Why are you using generics? You can simply declare `loadImage` as `func loadImage(forId id: UUID, completion: ((BaseImage) -> ())?)` – Paulw11 Jul 11 '17 at 09:47

3 Answers3

2

There's no need to use generics here. Just declare your completion handler as taking a BaseImage. It also allows you to factor the call to it out of the switch

func loadImage(forId id: UUID, completion: ((BaseImage) -> ())?) {

    let type = getType(forId: id)

    let image = FileSystemResourceLoader.loadResource(forId: id)
    let resultImage: BaseImage
    switch type {
    case .frontImage:
        resultImage = FrontImage(image: image)
    case .backImage:
        resultImage = BackImage(image: image)
    }
    completion?(resultImage)
}

In fact, you can do away with the image type enumeration (which is a bit of a code smell) quite easily. One way would be to change getType to return a closure that creates the image.

func getConstructor(forId id: UUID) -> ((Image) -> BaseImage)
{
    if id == "foo" // Jut an example
    {
        return { BackImage($0) }
    }
    else
    {
        return { FrontImage($0) }
    }
}

Then your loadImage becomes

func loadImage(forId id: UUID, completion: ((BaseImage) -> ())?) {

    let constructor= getConstructor(forId: id)

    let image = FileSystemResourceLoader.loadResource(forId: id)
    let resultImage = constructor(image)
    completion?(resultImage)
}
JeremyP
  • 84,577
  • 15
  • 123
  • 161
0

Replace the function ImageLoader with following code ... i have just changed

completion: ((T) -> ())? to
completion: ((BaseUIImage) -> ())

class ImageLoader<T: BaseUIImage> {
    func loadImage(forId id: UUID, completion: ((BaseUIImage) -> ())) {

        let type = getType(forId: id)

        switch type {
        case .frontImage:
            let image = FileSystemResourceLoader.loadResource(forId: id)
            let frontUIImage: FrontImage  = ImageConverter.convertToUIImage(image: image)
            completion(frontUIImage)
        case .backImage:
            let image = FileSystemResourceLoader.loadResource(forId: id)
            let backUIImage: BackImage  = ImageConverter.convertToUIImage(image: image)
            completion(backUIImage)
        }
    }

    func getType(forId id: UUID) -> ImageType {
        return .frontImage // simplified return value
    }
}
-1

First of all, your completion is defined as optional, so calling it without ? will produce error. Secondly, you should force cast to T, if you want to use generics.

Here is the changed loader code:

class ImageLoader<T: BaseUIImage> {
    func loadImage(forId id: UUID, completion: ((T) -> Void)?) {

        let type = getType(forId: id)

        switch type {
        case .frontImage:
            let image = FileSystemResourceLoader.loadResource(forId: id)
            let frontUIImage: FrontImage = ImageConverter.convertToUIImage(image: image)
            completion?(frontUIImage as! T)
        case .backImage:
            let image = FileSystemResourceLoader.loadResource(forId: id)
            let backUIImage: BackImage  = ImageConverter.convertToUIImage(image: image)
            completion?(backUIImage as! T)
        }
    }

    func getType(forId id: UUID) -> ImageType {
        return .frontImage // simplified return value
    }
}
Valerii Lider
  • 1,774
  • 16
  • 21
  • > Secondly, you should force cast to T, if you want to use generics. Why? – Zell B. Jul 11 '17 at 10:10
  • Looks like compiler do not understand that FrontImage is a T: BaseUIImage. I agree with another comment that you can get rid of generics here and just use completion with BaseImage. Why downvote? – Valerii Lider Jul 11 '17 at 10:21