2

I'm using this extension in order to retrieve HTTP headers within a urlSession callback:

extension HTTPURLResponse {

    func get(_ header: String) -> String? {
        let keyValues = allHeaderFields.map { (String(describing: $0.key).lowercased(), String(describing: $0.value)) }

        if let headerValue = keyValues.filter({ $0.0 == header.lowercased() }).first {
            return headerValue.1
        }
        return nil
    }

}

This works well and allows me to retrieve header elements like so:

    if let metaInt = httpResponse.get("icy-metaint") {
        self.icyMetaInt = Int(metaInt)
    }

Now I wanted it to make a little bit more elegant by introducing a generic function:

func getHeader<T>(_ httpResponse: HTTPURLResponse, _ headerName: String) -> T? {
        guard let element = httpResponse.get(headerName) else {
            return nil
        }
        return T(element)
}

The intention is to use statements like this:

self.icyMetaInt = getHeader(httpResponse, "icy-metaint")

I believe something like that would work in C# or so. But the Swift compiler complains:

Non-nominal type 'T' does not support explicit initialization

for

return T(element)

Even though if I use

return T.init(element) 

Is there any chance to achieve what I'm having in mind?

EDIT: Added T? (must have been lost while editing)

decades
  • 827
  • 17
  • 25
  • 1
    generics don’t work that way. Even if they did, you haven’t told the compiler what `T` is. – Paulw11 Nov 17 '17 at 10:53
  • Also, you are attempting to return `nil` from a function that doesn’t return an optional – Paulw11 Nov 17 '17 at 11:01
  • Yes, you are right. Need to add T?. However, this is the way generic functions work well in C#. The thing is, the compiler knows about T, because the assignment self.icyMetaInt (which is Int?) says it all. I don't see a reason, why that shouldn't work. But if it doesn't work in Swift, then it doesn't work. No issue – decades Nov 17 '17 at 11:28
  • The thing is: Swift allows me to make two functions like so: func getHeader(_ httpResponse: HTTPURLResponse, _ headerName: String) -> String? { guard let element = httpResponse.get(headerName) else { return nil } return String(element) } func getHeader(_ httpResponse: HTTPURLResponse, _ headerName: String) -> Int? { guard let element = httpResponse.get(headerName) else { return nil } return Int(element) } A "generic" solution would be ways better... – decades Nov 17 '17 at 11:51
  • The generic is not determined by the return context. It is determined by the object instance, so you would need an instance of `HTTPURLResponse` in order for the compiler to know that T is an Int, and then you couldn’t have another header value be interpreted as a String, and it also wouldn’t work because you don’t control the instantiation of the response object. – Paulw11 Nov 17 '17 at 19:49
  • While you could do what you want in C#, you would need to invoke the method as `getHeader(“icy-metaint”)` as from [here](https://msdn.microsoft.com/en-us/library/ms379564(v=vs.80).aspx) “Note that the compiler cannot infer the type based on the type of the returned value alone:”. swift doesn’t allow even this; you cannot specialise a generic function call; the generic type is established by the object instance – Paulw11 Nov 17 '17 at 20:11
  • Right. Exactly. Missing that in swift... Maybe it comes later – decades Nov 18 '17 at 23:35

1 Answers1

1

This happens because the compiler doesn't know which initializer it is supposed call, because what you are doing is simply too general, as the error states. What you are trying to achieve can be done, but the implementation depends on what you are achieve.

In short, if you want to be able to initialize a lot of different types, be it primitives or objects, you should define a new protocol, extend each type to implement this protocol as you see fit and have the T Type restricted to this protocol.

Example:

protocol HTTPHeaderProtocol {
    init(_ headerProtocolString: String?)
}

class MyCustomObject: HTTPHeaderProtocol {
    var myString = ""
    required init(_ headerProtocolString: String?) {
        self.myString = headerProtocolString ?? ""
    }
    
}

extension String: HTTPHeaderProtocol {
    init(_ headerProtocolString: String?) {
        self.init(headerProtocolString)
    }
}

extension Int: HTTPHeaderProtocol {
    init(_ headerProtocolString: String?) {
        self.init(headerProtocolString)
    }
}

extension HTTPURLResponse {
    
    func get(_ header: String) -> String? {
        let keyValues = allHeaderFields.map { (String(describing: $0.key).lowercased(), String(describing: $0.value)) }
        
        if let headerValue = keyValues.filter({ $0.0 == header.lowercased() }).first {
            return headerValue.1
        }
        return nil
    }
    func getHeader<T:HTTPHeaderProtocol>(_ httpResponse: HTTPURLResponse, _ headerName: String) -> T? {
        guard let element = httpResponse.get(headerName) else {
            return nil
        }
        return T(element)
    }
}

This will work, hope that helps:)

Community
  • 1
  • 1
Samer Murad
  • 2,479
  • 25
  • 27
  • 2
    Thanks. But this looks (no offense) really ugly :) I worked around by adding the variants to the HTTPURLResponse extension. IMHO it should be possible to do generics like I intend, because in that case it would allow to write "really" generic functions. The compiler should be able to detect, in what context the function is called in order to resolve the proper T. Nevermind. I have my solution. Thanks – decades Nov 17 '17 at 18:51