Following on from an answer to my previous question, I have 2 protocols…
protocol Filters: Encodable {
}
protocol QueryParameters: Encodable {
associatedtype T: Filters
var page: Int { get }
var filters: T { get }
}
and then for a type Transaction
, I have…
struct TransactionFilters: Filters {
var isWithdrawal: Bool
}
struct TransactionParamters<T: Filters>: QueryParameters {
var page: Int
var filters: T
}
All good so far.
Next I add a protocol, Filterable
, and I'd like any type that conforms to Filterable
to be able to return parameters, like this…
protocol Filterable {
func parameters() -> QueryParameters
}
struct Transactions: Filterable {
func parameters() -> QueryParameters {
let transactionFilters = TransactionFilters(isWithdrawal: true)
return TransactionParamters(page: 1, filters: transactionFilters)
}
}
but I end up with…
error: protocol 'QueryParameters' can only be used as a generic constraint because it has Self or associated type requirements
This seems a pretty straightforward requirement, but I've spent 2 days trying all combinations I could think of to get it to work. Now I'm finally admitting defeat.
What do I need to do to resolve this?
As I mentioned in the comment. What is missing in your code is that the associatedtype
never actually becomes a type. Nowhere in the code in one of your structs
you assign the type to the associatedtype
. If you want a generic filterable
functionality you could do something along those lines:
// Your generic Filters with required properties
protocol Filters: Encodable {
var isWithdrawal: Bool { get }
init(isWithdrawal: Bool)
}
// Your generic query parameters
protocol QueryParameters: Encodable {
associatedtype F: Filters
var page: Int { get }
var filters: F { get }
init(page: Int, filters: Filters)
}
// Filterable protocol will eventually accept any types conforming to Filters and QueryParameters
protocol Filterable {
associatedtype F: Filters
associatedtype P: QueryParameters
func parameters() -> P
}
// This is your generic Transactions struct
// With this you will be able to pass other types that meet the constraints
struct Transactions<ParameterType: QueryParameters>: Filterable {
typealias P = ParameterType
typealias F = ParameterType.F
func parameters() -> ParameterType {
return P(page: 1, filters: F(isWithdrawal: true))
}
}
You're done with the generic stuff. Now you can create different model objects which conform to your protocols
struct TransactionFilters: Filters {
private(set) var isWithdrawal: Bool // Conforming to filters
}
struct TransactionParameters<FilterType: Filters>: QueryParameters {
// Telling what type is the object that conforms to Filters
typealias F = FilterType
var page: Int
var filters: FilterType
init(page: Int, filters: Filters) {
self.page = page
self.filters = filters as! F
}
}
Create your transactions object like this:
let transactions = Transactions<TransactionParameters<TransactionFilters>>()
print(transactions.parameters().page)
print(transactions.parameters().filters.isWithdrawal)
You can create more types of QueryParameters and Filters
struct SomeOtherParameters<FilterType: Filters>: QueryParameters {
// You can do custom stuff in your SomeOtherParameters struct
// e.g. add an offset to page
typealias F = FilterType
var page: Int
var filters: FilterType
init(page: Int, filters: Filters) {
self.page = page + 100
self.filters = filters as! F
}
}
// Your special filter that always returns false
struct SomeOtherFilters: Filters {
init(isWithdrawal: Bool) {}
var isWithdrawal: Bool {
return false
}
}
let transactionsWithDifferentFilters = Transactions<SomeOtherParameters<SomeOtherFilters>>()
// You can combine any types that conform to you declared protocols
let evenMoreTransactions = Transactions<SomeOtherParameters<TransactionFilters>>()
print(evenMoreTransactions.parameters().page)
A little late, but I think it's still a good exercise to come up with a clean solution for your problem.
Within the Filterable
protocol, you can't use the protocol type QueryParameters
as a return type because QueryParameters
has an associatedtype
. Also, it's not really what you want, because returning a protocol type doesn't mean it conforms to itself (ie. QueryParameters
type doesn't conform to QueryParameters
).
Instead, what you need to do is create an associatedtype
that conforms to the protocol QueryParameters
to be used as a return type:
protocol Filterable {
associatedtype T : QueryParameters
func parameters() -> T
}
Then you can make Transactions
conform to Filterable
using the same function you have in your question but returning the opaque type some QueryParameters
rather than the protocol type QueryParameters
. This way you can return any type that conforms to QueryParameters
which is what you really wanted to do:
struct Transactions: Filterable {
func parameters() -> some QueryParameters {
let transactionFilters = TransactionFilters(isWithdrawal: true)
return TransactionParameters(page: 1, filters: transactionFilters)
}
}
I hope you find that helpful. BTW, I fixed the typo in TransactionParameters
in my answer :)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With