Reputation: 4976
I have a function as follows:
func request<D: Decodable>(from urlString: String,
useToken: Bool = false,
requestType: RequestType = .get,
body: Data? = nil,
expecting type: D.Type? = nil,
completion: @escaping (Result<D?>) -> Void)
Is it possible to do this: request(..., expecting: nil)
or func request<D: Decodable>(... expecting type: D.Type? = nil)
?
I'm thinking I've reached limitations to how generics can be used because when I do this I get compile errors that have absolutely nothing to do with the code I'm working on so I think the compiler might be getting confused.
When I use the function, such as: request(from: "https:..", requestType: .post, body: body)
, the compiler complains that Enum element 'post' cannot be referenced as an instance member
Some of my API requests don't return anything in the body so I'm trying to find a way to express that using this generic function I've set up
Upvotes: 5
Views: 2694
Reputation: 299345
The underlying problem here is that the type you want is Void, but Void is not Decodable, and you can't make it Decodable because non-nominal types (like Void) can't be extended. This is just a current limitation of Swift.
The right solution to this is overloading. Create two methods:
// For values
func request<D: Decodable>(from urlString: String,
useToken: Bool = false,
requestType: RequestType = .get,
body: Data? = nil,
expecting type: D.Type,
completion: @escaping (Result<D>) -> Void) {}
// For non-values
func request(from urlString: String,
useToken: Bool = false,
requestType: RequestType = .get,
body: Data? = nil,
completion: @escaping (Error?) -> Void) {}
Create another shared method that turns a request into Data and that both can call:
func requestData(from urlString: String,
useToken: Bool = false,
requestType: RequestType = .get,
body: Data? = nil,
completion: @escaping (Result<Data>) -> Void) {}
Your decoding request function will now convert .success(Data)
into a D
. Your non-decoding request function will throw away the data (or possibly ensure that it is empty if you're pedantic about it), and call the completion handler.
If you wanted your code to be a little more parallel, so that it always passes a Result rather than an Error?, then you can still have that with a tweak to the signature:
func request(from urlString: String,
useToken: Bool = false,
requestType: RequestType = .get,
body: Data? = nil,
completion: @escaping (Result<Void>) -> Void) {}
But overloading is still the answer here.
(OLD ANSWERS)
There's no problem with passing nil
here, as long as D
can somehow be inferred. But there has to be a way to infer D
. For example, the following should be fine:
request(from: "") { (result: Result<Bool?>) in
print(result)
}
What would not be fine would be this:
request(from: "") { (result) in
print(result)
}
Because in that case, there's no way to determine what D
is.
That said, given your goal, you don't want Type
to be optional anyway. As you say, sometimes the result is "returns nothing." The correct type for "returns nothing" is Void
, not nil.
func request<D: Decodable>(from urlString: String,
useToken: Bool = false,
body: Data? = nil,
expecting type: D.Type = Void.self, // <<----
completion: @escaping (Result<D>) -> Void)
(I'm assuming you then want Result<D>
rather than Result<D?>
, but either could be correct depending on your precise use case.)
Void
is a normal type in Swift. It is a type with exactly one value: ()
, the empty tuple.
Upvotes: 2
Reputation: 1341
this works fine for me in playground
let t = testStruct.init()
let t2 : testStruct? = nil
test(t)
testOptional(t)
testOptional(t2)
func test<T: testProtocol>(_ para: T){
print(para.id())
}
func testOptional<T: testProtocol>(_ para: T?){
if let p = para{
print(p.id())
}
}
protocol testProtocol {
func id() -> String
}
struct testStruct{
}
extension testStruct : testProtocol {
func id() -> String {
return "hello"
}
}
but you can't just call testOptional(). it has to be passed something, even a nil optional so the type can be inferred.
Upvotes: 0