Reputation: 2646
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
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