Reputation: 20379
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
Upvotes: 0
Views: 119
Reputation: 20379
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)])
}
}
Upvotes: 0
Reputation: 130152
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.
Upvotes: 1