Filip Zymek
Filip Zymek

Reputation: 2646

Swift generics - cannot convert value to type

I have an issue with generics in swift.

I am building simple http client and I want to extend it to add caching. I've build cache solution using this excellent post here.

I want to be able to pass cache instance to my client when it is initialised so it needs to be able to hold different data types that conform to Codable.

Here's what I've got so far (cache implementation is as described in article):

data:

struct CountryResult: Codable {
    let meta: Meta //confroms to Codable
    let results: [Country] //conforms to Codable
}

cache:

final class Cache<Key: Hashable, Value> {
// cache implementation
}

// MARK: - Codable

extension Cache.Entry: Codable where Key: Codable, Value: Codable {}
extension Cache: Codable where Key: Codable, Value: Codable {
    convenience init(from decoder: Decoder) throws {
        self.init()

        let container = try decoder.singleValueContainer()
        let entries = try container.decode([Entry].self)
        entries.forEach(insert)
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        try container.encode(keyTracker.keys.compactMap(entry))
    }
}

http client:

struct HttpService {

    let service: BaseUrl
    let cache: Cache<URL, Codable>

    init(service: String = "https://some.service/api", cache: Cache<URL, Codable> = Cache()) {
        self.service = service
        self.cache = cache
    }

    // performs http get
    func get<T: Codable>(endpoint: String, completion: @escaping (T?, Error?) -> Void) {
        guard let url = buildUrl(endpoint) else {
            print("Invalid url")
            return
        }

        if let cachedData = cache[url] as? T {
            print("Cache hit for: \(url)")
            completion(cachedData, nil)
            return
        } else {
            print("Cache miss for: \(url)")
        }

        //data not in cache, go to network
        //....
    }

Now, I want to be able to instantiate this http client in different view controllers that will request different datatypes. I also want to persist each VC cache separately, so when initialising VC I am trying to find if cache was stored to disk and load it like so:

let urls = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)
let cacheUrl = urls[0].appendingPathComponent("/v1/test.cache")
if let data = try? Data(contentsOf: cacheUrl) {
    let cache  = try? JSONDecoder().decode(Cache<URL, CountryResult>.self, from: data) ?? Cache<URL, CountryResult>()
    let httpClient = HttpService(service: "https://some.service/api", cache: cache) //ERROR
}

But I am getting following error, which I do not fully understand: Cannot convert value of type 'Cache<URL, CountryResult>?' to expected argument type 'Cache<URL, Codable>' (aka 'Cache<URL, Decodable & Encodable>')

Why it cannot be converted if CountryResult confroms to Codable aka Decodable & Encodable

Upvotes: 2

Views: 287

Answers (1)

Anand
Anand

Reputation: 1946

If you change the below code

let cache: Cache<URL, Codable>

as

let cache: Cache<URL,T: Codable>

in HttpService struct, hope it will work.

According to Swift doc, In Generics, it is allowed to have type parameter conforming to class or protocol or protocol composition but not just protocol.

Upvotes: 2

Related Questions