I have a generic type (Bar
) that shadows the implementation of the function foo
depending on T
:
struct Bar<T> {
func foo() {
print("foo generic")
}
func foo() where T == String {
print("foo string")
}
}
When I call it, it works like I expect it to:
B<Double>().foo() // foo generic
B<String>().foo() // foo string
Now I create an another function that calls foo
inside the object:
func boo() { foo() }
But when I call boo
, it doesn't use the string "shadow" altogether.
B<String>().boo() // foo generic
@_disfavoredOverload
but it didn't do anything.foo
function to another object. And I was surprised when this didn't work. I'd like to know why this solution didn't work.struct Bar<T> {
let inner: Far<T>
func boo() {
inner.foo()
}
}
struct Far<T> {
func foo() {
print("foo generic")
}
func foo() where T == String {
print("foo string")
}
}
I ran into this issue while creating a SwiftUI body. So I'm unable to use some solutions.
// Very simplified.
struct MyView<APIClient: SomeClientProtocol>: View {
let client: APIClient
var body: some View {
Button("Fetch") {
Task(operation: fetchData) // Always calls generic
}
}
func fetchData() async {
await client.fetch()
}
func fetchData() async where APIClient: FetchSpecially {
await client.specialFetch()
}
}
In your first example, boo
doesn't have a specialized overload, this is why the compiler always dispatches to the the generic overload. There is only one implementation for boo
, and since for generics the dispatch decisions take time at compile time, you end up with boo
always calling the non-specialized overload of foo
.
If you were to add a specialized func boo() where T == String { foo() }
, you'll see that the desired foo
implementation is called. At this point the compiler generates two implementations for boo
, and the callers of boo
can pick the specialized one that will forward the call to the specialized foo
. However this is not a scalable approach, as adding new specializations of the foo
method will have to be carefully synced with boo
, there is no help from the compiler here.
The same happens in the second example, the body
property of the SwiftUI view doesn't operate on any specializations, this is why it always dispatches to the general, non-specialized fetch
function. If it were a function you could've also specialize it for the more specific protocol, but that might've led to code duplication.
For your particular case the solution is to construct the Task
closure at initialization time, this way the compiler knows where to dispatch:
struct MyView<APIClient: SomeClientProtocol>: View {
private let fetchData: @MainActor () async -> Void
init(client: APIClient) {
fetchData = {
await client.fetch()
}
}
init(client: FetchSpecially) {
fetchData = {
await client.specialFetch()
}
}
var body: some View {
Button("Fetch") {
Task(operation: fetchData) // Calls generic or specialized, depending on how the view was instantiated
}
}
}
With the above setup in place, the compiler will pick the appropriate initializer, assuming you don't indirectly pass the client
as the base protocol - i.e. the call site knows if the client is an FetchSpecially
or not.