I have a static function in a class that takes a generic type that must conform to decodable, however when I call this function I get the following error: "Type of expression is ambiguous without more context". The Tour class (which is the type I'm passing to the function) conforms to Decodable and inherits from the CoreDataModel class.
This is occurring on the new Xcode 12.0 Beta in DashboardNetworkAdapter when I call CoreDataModel.create (line 7 on the snippet I've shared for that class).
Edit minimal reproducible example:
DashboardNetworkAdapter:
class DashboardNetworkAdapter {
// MARK: - GET
public func syncTours(page: Int, completion: @escaping(Bool, Error, Bool) -> Void) {
if let path = Bundle.main.path(forResource: "Tours", ofType: "json") {
do {
let data = try Data(contentsOf: URL(fileURLWithPath: path), options: .mappedIfSafe)
CoreDataModel.create(Array<Tour>.self, from: data) { success, error in
completion(success, error, false)
}
} catch let error {
completion(false, error, false)
}
}
}
}
CoreDataModel:
public class CoreDataModel: NSManagedObject {
// MARK: - Variables
@NSManaged public var id: Int64
@NSManaged public var createdAt: Date?
@NSManaged public var updatedAt: Date?
// MARK: - CRUD
static func create<T>(_ type: T.Type, from data: Data, completion: @escaping (Bool, Error?) -> Void) where T : Decodable {
DataCoordinator.performBackgroundTask { context in
do {
let _ = try DataDecoder(context: context).decode(type, from: data, completion: {
completion(true, nil)
})
} catch let error {
completion(false, error)
}
}
}
}
Tour:
@objc(Tour)
class Tour: CoreDataModel, Decodable {
// MARK: - Variables
@NSManaged public var name: String?
@NSManaged public var image: URL?
@NSManaged public var owned: Bool
@NSManaged public var price: Double
// MARK: - Coding Keys
enum CodingKeys: String, CodingKey {
case id = "id"
case name = "name"
case image = "image"
case owned = "owned"
case price = "price"
case updatedAt = "updated_at"
case createdAt = "created_at"
}
// MARK: - Initializer
required convenience init(from decoder: Decoder) throws {
guard let context = decoder.userInfo[.context] as? NSManagedObjectContext else { fatalError("Cannot find decoding context")}
self.init(context: context)
let topLevel = try decoder.container(keyedBy: PaginatedResponseCodingKeys.self)
let values = try topLevel.nestedContainer(keyedBy: CodingKeys.self, forKey: .data)
let id = try values.decode(Int64.self, forKey: .id)
let name = try values.decode(String.self, forKey: .name)
let image = try values.decode(String.self, forKey: .image)
let owned = try values.decode(Int.self, forKey: .owned)
let price = try values.decode(Double.self, forKey: .price)
let updatedAt = try values.decode(Date.self, forKey: .updatedAt)
let createdAt = try values.decode(Date.self, forKey: .createdAt)
self.id = id
self.name = name
self.image = URL(string: image)
self.owned = owned == 1
self.price = price
self.updatedAt = updatedAt
self.createdAt = createdAt
}
init(context: NSManagedObjectContext) {
guard let entity = NSEntityDescription.entity(forEntityName: "Tour", in: context) else { fatalError() }
super.init(entity: entity, insertInto: context)
}
}
DataDecoder:
class DataDecoder: JSONDecoder {
// MARK: - Variables
var context: NSManagedObjectContext!
private var persistent: Bool = true
// MARK: - Initializers
public init(persistent: Bool = true, context: NSManagedObjectContext) {
super.init()
self.dateDecodingStrategy = .custom({ (decoder) -> Date in
let container = try decoder.singleValueContainer()
let string = try container.decode(String.self)
let dateFormatter = DateFormatter()
locale = .autoupdatingCurrent
dateFormat = "yyyy-MM-dd HH:mm:ss"
return dateFormatter.date(from: string)!
})
self.context = context
userInfo[.context] = context
}
// MARK: - Utilities
public func decode<T>(_ type: T.Type, from data: Data, completion: (() -> Void)? = nil) throws -> T where T : Decodable {
let result = try super.decode(type, from: data)
if(persistent) {
saveContext(completion: completion)
}
return result
}
private func saveContext(completion: (() -> Void)? = nil) {
guard let context = context else { fatalError("Cannot Find Decoding Context") }
context.performAndWait {
try? context.save()
completion?()
context.reset()
}
}
}
CodingUserInfoKey Extension:
extension CodingUserInfoKey {
static let context = CodingUserInfoKey(rawValue: "context")!
}
There's a type mismatch between what completion
closure expects for its Error
parameter, and what it gets from the inferred type of the CoreDataModel.create
completion closure, which is Error?
:
public func syncTours(page: Int, completion: @escaping(Bool, Error, Bool) -> Void) {
...
CoreDataModel.create(Array<Tour>.self, from: data) { success, error in
// error is of type Error?, but completion expects Error
completion(success, error, false)
}
...
}
In general, whenever you face type inference issues or errors, make each type explicit, and you'll see exactly exactly where the mismatch is. For example, below you could be explicit about the inner closure signature:
CoreDataModel.create([Tour].self, from: data) { (success: Bool, err: Error?) -> Void in
}