Reputation: 347
I have a protocol called ContentService that is intended to expose common functionality for reading data from various REST APIs. Each implementation of ContentService will perform the mechanics required by that particular API.
protocol ContentService {
func loadNextPage<T>(pageStartId: T) -> [Datum]
//Other methods...
}
Since a given API may have a different way of marking page boundaries of returned data, I want to specify the required type in the implementing class as follows:
class ServiceA : ContentService {
func loadNextPage<Int>(pageStartId: Int) -> [Datum] {
//Do something with pageStartId typed as an Int
return posts
}
}
class ServiceB : ContentService {
func loadNextPage<NSDate>(pageStartId: NSDate) -> [Datum] {
//Do something with pageStartId typed as an NSDate
return posts
}
}
NOTE that I'm specifying the type parameter in the definition of my implementing types (ServiceA, ServiceB) because they are specific to that service. My expectation is that any instance of these classes would have a method with signature loadNextPage(pageStartId: X) -> [Post]
where X is the specific type supplied for T when implementing the method.
In order to load a page of data from the api, I would therefore use something like this:
let apiA = ServiceA()
let apiB = ServiceB()
let dataA = apiA.loadNextPage(1234)
let dataB = apiB.loadNextPage(NSDate())
While this compiles without errors. You can also compile the following without errors:
let dataA = apiA.loadNextPage("Anything works here.")
Therefore, ServiceA's loadNextPage() method isn't being restricted to Int parameters. Furthermore, in the method definitions for the loadNextPage() method, although the pageStartId parameter seems to be of the expected type when inspecting it in Xcode, I can't use any operators or methods that would normally be accessible on that type. For example, in ServiceA's implementation I can't do let newId = pageStartId + 5
even though pageStartId should be an Int.
Two questions:
1) Obviously I'm misunderstanding something about generic methods in Swift and would like to know why this pattern can't be used.
2) If anyone has a clever solution to achieve want I'm trying above, I'd love to know it.
Upvotes: 1
Views: 885
Reputation: 539745
Your protocol
protocol ContentService {
func loadNextPage<T>(pageStartId: T) -> [Datum]
//Other methods...
}
requires a generic method loadNextPage
, and
func loadNextPage<Int>(pageStartId: Int) -> [Datum] {
//Do something with pageStartId typed as an Int
return posts
}
is exactly that: A generic method where the placeholder happens to
have the name Int
. Inside the method, Int
refers to that placeholder type and hides the global Swift type Int
. An equivalent definition would be
func loadNextPage<FOO>(pageStartId: FOO) -> [Datum] {
//Do something with pageStartId typed as an Int
return posts
}
What you probably want is to define a protocol with an associated type T
:
protocol ContentService {
associatedtype T
func loadNextPage(pageStartId: T) -> [Datum]
//Other methods...
}
and a class which adopts this protocol with T == Int
:
class ServiceA : ContentService {
func loadNextPage(pageStartId: Int) -> [Datum] {
let newId = pageStartId + 5 // <-- This compiles now!
//Do something with pageStartId typed as an Int
return posts
}
}
Now
let dataA = apiA.loadNextPage(1234)
compiles, but
let dataA2 = apiA.loadNextPage("Anything works here.")
// error: cannot convert value of type 'String' to expected argument type 'Int'
doesn't, as expected.
Upvotes: 4