Cannot convert value of type '[DataModel<T>]' to expected argument type '[DataModel<T>]'

100 Views Asked by At

I have a data model where product can change based on the experimentation (AB Testing), so I have a struct with generics

struct DataModel<T: Sendable & Equatable> {
    var product: T?
    // there are other properties,
}

I have data provider and a protocol

protocol SomeProtocol: AnyObject {
    func fetchedData<T: Sendable & Equatable>(data: [DataModel<T>])
}

class DataProvider {
    weak var delegate: SomeProtocol?

    func getData() {
        //this is a async flow for simplicity am adding synchronous code here
        var someObjects = [DataModel<SKProduct>]()
        delegate?.fetchedData(data: someObjects)
    }
}

Finally a UIViewController

class SomeViewController<T: Sendable & Equatable>: UIViewController {
    func renderUI(data: [DataModel<T>]) {
        debugPrint(data)
    }
}

extension SomeViewController: SomeProtocol {
    func fetchedData<T>(data: [DataModel<T>]) {
        renderUI(data: data)
    }
}

But I am getting the following error at line renderUI(data: data)

Cannot convert value of type '[SomeRandomProject.DataModel<T>]' to expected argument type '[SomeRandomProject.DataModel<T>]'

EDIT 1:

Following @Sulthan's answer below I modified my code to

protocol SomeProtocol: AnyObject {
    associatedtype DataType: Sendable & Equatable
    func fetchedData(data: [DataModel<DataType>])
}

class DataProvider {
    weak var delegate: (any SomeProtocol)?

    func getData() {
        var someObjects = [DataModel<SKProduct>]()
        delegate?.fetchedData(data: someObjects)
    }
}

class SomeViewController<T: Sendable & Equatable>: UIViewController, SomeProtocol {
    typealias DataType = T

    func fetchedData(data: [DataModel<T>]) {
        renderUI(data: data)
    }

    func renderUI(data: [DataModel<T>]) {
        debugPrint(data)
    }
}

Though this solves the issue at line renderUI(data: data) I get new error now at delegate?.fetchedData(data: someObjects)

Cannot convert value of type '[DataModel<SKProduct>]' to expected argument type '[DataModel<(any SomeProtocol).DataType>]'
Member 'fetchedData' cannot be used on value of type 'any SomeProtocol'; consider using a generic constraint instead
2

There are 2 best solutions below

3
On BEST ANSWER

The problem is that the generic method in SomeProtocol can accept any type T but the renderUI can accept only the type that SomeViewController has been initialized with.

For example, if you define SomeViewController<String> then SomeProtocol says you should be able to call fetchedData<Int> which would then have to pass an Int to SomeViewController<String>.

There is no simple way to fix it, the architecture must be changed depending on what you really need.

Maybe:

protocol SomeProtocol: AnyObject {
    associatedtype DataModelType: Sendable & Equatable

    func fetchedData(data: [DataModel<DataModelType>])
}

is what you want for the view controller. However, that wouldn't work with the data provider without changes.

0
On

Here is how I solved this problem, am not sure if this is the efficient or even the correct way, but as of now this solves the problem I mentioned in the question. If I bump into any issue with this approach in future, Ill probably comeback and update the answer to reflect the same.

As suggested by Sulthan I decided to use the associatedtype in protocol to fix (Its very well explained in answer above as to why this error occurs and what it means)

Cannot convert value of type '[SomeRandomProject.DataModel<T>]' to expected argument type '[SomeRandomProject.DataModel<T>]'

But issue with using associated type in protocol is, you cant use protocol as concrete type and hence compiler will force you to use any as shown in my edit 1 of the question. This will obviously lead to

Cannot convert value of type '[DataModel<SKProduct>]' to expected argument type '[DataModel<(any SomeProtocol).DataType>]'
Member 'fetchedData' cannot be used on value of type 'any SomeProtocol'; consider using a generic constraint instead

on statement delegate?.fetchedData(data: someObjects)

Because Protocol with associated type cant be used as concrete type I decided to introduce a proxy class and make it to confirm SomeProtocol

class SomeProxyClass<T: Equatable>: SomeProtocol {
    typealias DataType = T
    var fetchDataCompletionBlock: (([DataModel<T>]) -> ())?

    func fetchedData(data: [DataModel<DataType>]) {
        fetchDataCompletionBlock?(data)
    }
}

Now updated my DataProvider to hold delegate of type SomeProxyClass<T> instead of SomeProtocol

class DataProvider<T: Equatable> {

    weak var delegate: SomeProxyClass<T>?
    func getData() {
        delegate?.fetchedData(data: [DataModel(value: 100 as! T)])
    }
}

Finally in my ViewController I instantiated the instance of SomeProxyClass<T> and passed it as delegate to DataProvider. But now when DataProvider has fetched the data it will inform SomeProxyClass instead of my ViewController. To get a call back from SomeProxyClass to my ViewController I used closure like fetchDataCompletionBlock

Overall code looks like

struct DataModel<T: Equatable> {
    let value: T
}

class ViewController1<T: Equatable>: UIViewController {

    func someFunction() {
        let dataProvider = DataProvider<T>()
        let proxyInstance = SomeProxyClass<T>()
        proxyInstance.fetchDataCompletionBlock = {[weak self] dataArray in
            debugPrint("do whatever you want with \(dataArray)")
        }
        dataProvider.delegate = proxyInstance
        dataProvider.getData()
    }
}

protocol SomeProtocol: AnyObject {
    associatedtype DataType: Equatable
    func fetchedData(data: [DataModel<DataType>])
}

class SomeProxyClass<T: Equatable>: SomeProtocol {
    typealias DataType = T
    var fetchDataCompletionBlock: (([DataModel<T>]) -> ())?

    func fetchedData(data: [DataModel<DataType>]) {
        fetchDataCompletionBlock?(data)
    }
}

class DataProvider<T: Equatable> {

    weak var delegate: SomeProxyClass<T>?
    func getData() {
        delegate?.fetchedData(data: [DataModel(value: 100 as! T)])
    }
}