Is it possible for a Swift type to be inferred by "pulling out" a Type value from a generic function's parameter? - swift

Introduction
(Apologies if the title is confusing, but I explain the question better here!)
I'm building a networking library that can perform JSON decoding on its responses.
Host apps adopting this library will create enums conforming to NetLibRoute. All that currently does is enforce the presence of asURL:
public protocol NetLibRoute {
var asURL: URL { get throws }
}
In a host app, I have a routing system that enforces API structure at the compiler-level (via enums and associated values) for each endpoint, like this:
enum Routes: NetLibRoute {
case people(Int?)
// Other routes go here, e.g.:
// case user(Int)
// case search(query: String, limit: Int?)
var asURL: URL {
let host = "https://swapi.dev/"
let urlString: String
switch self {
case let .people(personID):
if let personID {
urlString = host + "api/people/\(personID)"
} else {
urlString = host + "api/people/"
}
// Build other URLs from associated values
}
return URL(string: urlString)!
}
}
I also want each enum to be associated with a certain Codable type. I can do that, of course, by modifying the Route protocol declaration to also require a type conforming to Decodable:
protocol NetLibRoute {
var asURL: URL { get throws }
var decodedType: Decodable.Type { get } // This
}
And a matching computed property in my Routes enum:
var decodedType: Decodable.Type {
switch self {
case .people(_):
return Person.self
// And so on
}
}
The Problem
Currently, my networking code has a declaration something like this:
public static func get<T>(route: NetLibRoute,
type: T.Type) async throws -> T where T: Decodable {
// performing request on route.asURL
// decoding from JSON as T or throwing error
// returning decoded T
}
Which lets me call it like this:
let person = try await NetLib.get(route: Routes.people(1), type: Person.self)
However, this redundancy (and potential human error from mismatching route and type) really irks me. I really want to be able to only pass in a route, and have the resulting type be inferred from there.
Is there some way to get the compiler to somehow check the NetLibRoute enum and check its decodedType property, in order to know what type to use?
Ultimately, I want this networking function to take one parameter (a route) and infer the return type of that route (at compile-time, not with fragile runtime hacks or !s), and return an instance of the type.
Is this possible?
Potential Alternatives?
I'm also open to alternative solutions that may involve moving where the get function is called from.
For example, calling this get function on a route itself to return the type:
let person = try await Routes.people(1).get(type: Person.self) // Works, but not optimal
let person = try await Routes.people(1).get() // What I want
Or even on the type itself, by creating a new protocol in the library, and then extending Decodable to conform to it:
public protocol NetLibFetchable {
static var route: NetLibRoute { get }
}
extension Decodable where Self: NetLibFetchable {
public static func get<T>() async throws -> T where Self == T, T: Decodable {
// Call normal get function using inferred properties
return try await NetLib.get(route: route,
type: T.self)
}
Which indeed lets me call like this:
let person = try await Person.get() // I can't figure out a clean way to pass in properties that the API may want, at least not without once again passing in Routes.people(1), defeating the goal of having Person and Routes.people inherently linked.
While this eliminates the issue of type inference, the route can no longer be customized at call-time, and instead is stuck like this:
extension Person: NetLibFetchable {
public static var route: NetLibRoute {
Routes.people(1) // Can't customize to different ID Ints anymore!
}
}
Which makes this particular example a no-go, and leaves me at a loss.
Appreciation
Anyway, thank you so much for reading, for your time, and for your help.
I really want this library to be as clean as possible for host apps interacting with it, and your help will make that possible.

Are you wedded to the idea of using an enum? If not, you can do pretty much what you want by giving each enum value its own type and using an associated type to do what you want.
public protocol NetLibRoute
{
var asURL: URL { get throws }
associatedtype Decoded: Decodable
}
struct Person: Decodable
{
var name: String
}
struct Login: Decodable
{
var id: String
}
struct People: NetLibRoute
{
typealias Decoded = Person
var id: Int
var asURL: URL { return URL(filePath: "/") }
}
struct User: NetLibRoute
{
typealias Decoded = Login
var id: String
var asURL: URL { return URL(filePath: "/") }
}
func get<N: NetLibRoute>(item: N) throws -> N.Decoded
{
let data = try Data(contentsOf: item.asURL)
return try JSONDecoder().decode(N.Decoded.self, from: data)
}
let thing1 = try get(item: People(id: 1))
let thing2 = try get(item: User(id: "foo"))
Where you might have had a switch before to do different things with different Routes you would now use a function with overloaded arguments.
func doSomething(thing: Person)
{
// do something for a Person
}
func doSomething(thing: Login)
{
// do something else for a Login
}
doSomething(thing: thing1)
doSomething(thing: thing2)

I think the problem lays in this function.
public static func get<T>(route: Route,
type: T.Type) async throws -> T where T: Decodable {
// performing request on route.asURL
// decoding from JSON as T or throwing error
// returning decoded T
}
On the first hand, it uses concretions instead of abstractions. You shouldn't pass a Route here, it should use your protocol NetLibRoute instead.
On the other hand, I think that the type param is not needed. Afaik you can get the Type to Decode with the var:
NetLibRoute.decodedType
Am I missing something on this matter?
Apart from that, I'd rather go with struct instead of enum when trying to implement the Routes (concretions). Enums cannot be extended. So you won't be allowing the creation of new requests in client side, only in the library.
I hope I've helped.
PS: Some time ago I made this repo. Maybe that could help you (specially this class). I used Combine instead of async/await, but it's not relevant to what you need.

Related

Inferring a generic type from its nested type

I am trying to create a Fetchable protocol that contains the location of where to get the objects from as part of its type, and instead of writing the fetch function with an explicit type parameter, like this:
func fetch<Model: Fetchable>(_ type: Model.Type, path: Model.Path) -> AnyPublisher<[Model], Error> {
print(path.value)
// ...
}
I would like Model to be inferred from the Model.Path parameter:
func fetch<Model: Fetchable>(path: Model.Path) -> AnyPublisher<[Model], Error> {
print(path.value)
// ...
}
This is inspired by #RobNapier's approach here. It's not exactly the same, and so I might be missing salient details to make it work.
Here's what I have:
protocol Locatable {
associatedtype Model
var value: String { get }
}
protocol Fetchable: Codable {
associatedtype Path: Locatable where Path.Model == Self
}
struct Message {
let content: String
}
extension Message: Fetchable, Codable {
enum Path: Locatable {
typealias Model = Message
case forUser(_ userId: String)
var value: String {
switch self {
case .forUser(let userId): return "/user/\(userId)/messages"
}
}
}
}
When I call fetch, I get an error "Generic parameter 'Model' could not be inferred"
let pub = fetch(path: Message.Path.forUser("123"))
But this works with a fetch that accepts the type parameter explicitly (even infers its own Message.Path type):
let pub = fetch(Message.self, .forUser("123"))
Any idea how (if possible) to solve this?
It is not enough information to infer, but if we write
let pub: AnyPublisher<[Message], Error> = fetch(path: Message.Path.forUser("123"))
everything goes well.
Update: nested type is just a type it is not dividable, so to help swift to infer parent we need to reverse declaration, like below (tested with Xcode 12.1):
func fetch<Path: Locatable>(path: Path) ->
AnyPublisher<[Path.Model], Error> where Path.Model: Fetchable {
and now your desired expression becomes possible
let pub = fetch(path: Message.Path.forUser("123"))

How to make a function that returns a decodable type in Swift?

So I have this enum that I use for the few url requests I use in my app :
enum Netwrok {
case popular
case topRated
case latest
// ...
static let baseUrl = "http://..."
func path() -> String {
switch self {
case .popular: return "/popular"
// ...
}
}
}
And I would like to add a function that returns the Decodable Type of model the network stack should decode the data with.
So I thought something like that would do the job :
func returnType<T>() -> T.Type where T : Decodable {
switch self {
case .popular:
return Popular.self
// ...
}
}
But I can't make it work, it says :
Cannot convert return expression of type 'Popular.Type' to return type 'T.Type'
Asking me to force cast in T.Type.
How can I make a function that returns the decodable so that type can be handled but the JSONDecoder's decode function ?
Thanks.
What you're asking is straightforward, but it probably isn't what you want. What you're asking to do is to return a type. There's nothing generic about that.
func returnType<T>() -> T.Type where T : Decodable {
This syntax defines a type parameter, T, that is passed by the caller. It's not defined by your function. That means the caller may pass any type that is Decodable and your function will return it. For example, the caller can set T to be Int (since that's Decodable), and you will return Int.Type. That's easy to implement (return T.self), but not what you mean.
What you mean is that the function returns some type that is Decodable that the function knows, but the caller doesn't:
func returnType() -> Decodable.Type { ... }
This will work fine, and do exactly what you are asking for, but it suggests you're probably building this network stack incorrectly and will have headaches later.
The reason this approach is likely to be a problem is that you probably want to write a line of code like this:
let result = JSONDecoder().decode(networkType.returnType(), from: data)
That's going to break, because Decodable.Type is not itself a Decodable type. (You you decode Int, but you can't decode the type of Int.) Say it did work. What type would result be? What could you do with it? The only thing you'd know about it is that it's Decodable (and you've already decoded it).
You likely want something more like Vasu Chand's implementation, or the similar approach discussed in my blog series.
You can use escaping closure for your returning result of an API Call.
Assuming you are hitting a get request . A simple working example for passing Codable model for get request api.
class func GETRequest<ResponseType :Decodable>(url : URL,responseType : ResponseType.Type ,completion: #escaping (ResponseType? ,Error? ) -> Void){
var request = URLRequest(url: url)
request.httpMethod = "GET"
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
guard let data = data else{
completion(nil,error)
return
}
let decoder = JSONDecoder()
do{
let responseData = try decoder.decode(ResponseType.self, from: data)
completion(responseData, nil)
}
catch let error{
completion(nil, error)
}
}
task.resume()
}
How to call this network function.
Network.GETRequest(url: url, responseType: Model.self) { (model, error) in
completion(model,error)
}
Model class contains
struct Model : Codable{
}
You can pass any response model for any get request to network class .
Similarly you can build api network for post request where request body is simply Codable model .
For sorry you can't as according to your need the supply for the first parameter here
JSONDecoder().decode(AdecodableType.self,from:data)
need to be inferred right when you write the code so it can't be Any 1 from a collection of types that conform to Decodable

Creating a property of strict generic type of "self"

I want to create a property on a class that uses the class type as a generic parameter, and I'm having difficulty working it out.
open class ResponseProcessor {
required public init() {
}
var success: ((_ responseProcessor: ResponseProcessor) -> Void)?
func process() {
success?(self)
}
}
class TestProcessor: ResponseProcessor {
var result: String?
override func process() {
result = "Some Result"
super.process()
}
}
open class Request<ResponseProcessorType: ResponseProcessor> {
var success: ((_ responseProcessor: ResponseProcessor) -> Void)?
func doRequest() {
let responseProcessor = ResponseProcessorType.init()
responseProcessor.success = success
responseProcessor.process()
}
}
class TestRequest: Request<TestProcessor> {
}
let testRequest = TestRequest()
testRequest.success = { (responseProcessor) in
// This line reports an error, but I want it to know what
// type the responseProcessor is.
print(responseProcessor.result)
}
testRequest.doRequest()
I want to be able to assign SubRequest to the .request variable, but I can't because of strict generic typing.
So I'd like to be able to say "the request property on a ResponseProcessor should be of type Request<WhateverThisClassIs>, but I can't work out how to express that, or declare it in a way that works.
It should work out that testProcessor.request is of type HTTPRequest<TestProcessor>, but obviously that isn't happening.
I'm not sure if this is going to answer your question or not, but maybe it will put you on a better road. To your stated question, the answer is there is no generic covariance in Swift. What you're trying to write is not possible. Generic covariance wouldn't actually fix your code, because you have a lot of other type problems here (your latest version is probably violating Liskov's Substitution Principle, which means it breaks the meaning of class inheritance). But I don't think you actually want what you're trying to write at all.
I suspect you're writing a pluggable and testable networking stack. That's really common. He's a fairly simple one; they can get much more powerful if you tear this apart a bit more.
First, the low-level networking stack itself should consume URLRequests and return Data. That's all. It should not try to deal with model types. This is where people always go off the rails. So a Request is an URLRequest and a completion handler:
struct Request {
let urlRequest: URLRequest
let completion: (Result<Data, Error>) -> Void
}
And a client consumes those.
final class NetworkClient {
func fetch(_ request: Request) {
URLSession.shared.dataTask(with: request.urlRequest) { (data, _, error) in
if let error = error { request.completion(.failure(error)) }
else if let data = data { request.completion(.success(data)) }
}.resume()
}
}
Now we generally don't want to talk to URLSession when we're testing. We want to throw back pre-canned data probably. So we make one of those.
final class TestClient {
enum ClientError: Error {
case underflow
}
var responses: [Result<Data, Error>]
init(responses: [Result<Data, Error>]) { self.responses = responses }
func fetch(_ request: Request) {
if let response = responses.first {
responses.removeFirst()
request.completion(response)
} else {
request.completion(.failure(ClientError.underflow))
}
}
}
I'm marking things final class because these are sensibly reference types, but I want to make it clear that I'm not using class inheritance anywhere here. (Feel free to leave "final" off in your own code; it's a bit pedantic and usually not needed.)
How are these two things alike? They share a protocol:
protocol Client {
func fetch(_ request: Request)
}
Great. Now I can do things like:
let client: Client = TestClient(responses: [])
No associated types means that Client is perfectly fine as a type.
But getting back Data is kind of ugly. We want a type, like User.
struct User: Codable, Equatable {
let id: Int
let name: String
}
How do we do that? We just need a way to construct a Request that fetches a Decodable:
extension Request {
init<Model: Decodable>(fetching: Model.Type,
from url: URL,
completion: #escaping (Result<Model, Error>) -> Void) {
self.urlRequest = URLRequest(url: url)
self.completion = { data in
completion(Result {
try JSONDecoder().decode(Model.self, from: data.get())})
}
}
}
Notice how Request still doesn't know anything about models? And Client doesn't know anything about models. There's just this Request initializer that takes a Model type and wraps it up in a way that can accept Data and spit back a Model.
You can take this approach miles further. You can write a Client that wraps a Client and modifies the request, adding headers for example.
struct AddHeaders: Client {
let base: Client
let headers: [String: String]
func fetch(_ request: Request) {
var urlRequest = request.urlRequest
for (key, value) in headers {
urlRequest.addValue(value, forHTTPHeaderField: key)
}
base.fetch(Request(urlRequest: urlRequest,
completion: request.completion))
}
}
let client = AddHeaders(base: NetworkClient(),
headers: ["Authorization": "Token ...."])
There are no subclasses here, no generic types, just one protocol (which has no associated types), and one generic method. But you can plug in a wide variety of back-ends, and compose together any operation that can be made to match one of a handful of transforms (Request -> Request, Request -> Data, Data -> Void).
I hope this matches some of what you're getting at with your question. Best of luck.

How can I call a static function on a protocol in a generic way?

Is there a point to declaring a static function on a protocol? The client using the protocol has to call the function on a type conforming to the protocol anyway right? That breaks the idea of not having to know the type conforming to the protocol IMO. Is there a way to call the static function on the protocol in a way where I don't have to know the actual type conforming to my protocol?
Nice question. Here is my humble point of view:
Is there a point to declaring a static function on a protocol?
Pretty much the same as having instance methods declared in a protocol.
The client using the protocol has to call the function on a type conforming to the protocol anyway right?
Yes, exactly like instance functions.
That breaks the idea of not having to know the type conforming to the protocol IMO.
Nope. Look at the following code:
protocol Feline {
var name: String { get }
static func createRandomFeline() -> Feline
init()
}
extension Feline {
static func createRandomFeline() -> Feline {
return arc4random_uniform(2) > 0 ? Tiger() : Leopard()
}
}
class Tiger: Feline {
let name = "Tiger"
required init() {}
}
class Leopard: Feline {
let name = "Leopard"
required init() {}
}
let feline: Feline = arc4random_uniform(2) > 0 ? Tiger() : Leopard()
let anotherFeline = feline.dynamicType.createRandomFeline()
I don't know the real type inside the variable feline. I just know that it does conform to Feline. However I am invoking a static protocol method.
Is there a better way to do this?
I see, you would like to call a static method/function declared in a protocol without creating a value that conforms to the protocol.
Something like this:
Feline.createRandomFeline() // DANGER: compiler is not happy now
Honestly I don't know the reason why this is not possible.
yes this is possible:
Swift 3
protocol Thing {
static func genericFunction()
}
//... in another file
var things:[Thing] = []
for thing in things {
type(of: thing).genericFunction()
}
Thank you #appzYourLife for the help! Your answer inspired my answer.
#appzYourLife answered my question. I had an underlying issue I was trying to resolve and the following code resolves my issue, so I'll post this here, maybe it helps someone with my same underlying question:
protocol MyProtocol {
static func aStaticFunc()
}
class SomeClassThatUsesMyProtocolButDoesntConformToIt {
var myProtocolType: MyProtocol.Type
init(protocolType: MyProtocol.Type) {
myProtocolType = protocolType
}
func aFunction() {
myProtocolType.aStaticFunc()
}
}
I created another solution for this case. IMHO this is quite clean and simple.
First, create a protocol for accessing instance type.
protocol TypeAccessible {
func type() -> Self.Type
}
extension TypeAccessible {
func type() -> Self.Type {
return Swift.type(of: self)
}
}
then create your concrete class as here. The point is your protocol should conform to TypeAccessible protocol.
protocol FooProtocol: TypeAccessible {
static func bar()
}
class Foo: FooProtocol {
static func bar() { }
}
On call site use it as
let instance: FooProtocol = Foo()
instance.type().bar()
For further use cases, just make sure your protocols conform to TypeAccessible and that's all.
A little late to the party on this one.
Here's my solution for "adding" static properties/functions/types to a protocol using typealias.
For example:
enum PropertyScope {
case all
case none
}
struct PropertyNotifications {
static var propertyDidChange =
Notification.Name("propertyDidChangeNotification")
}
protocol Property {
typealias Scope = PropertyScope
typealias Notifications = PropertyNotifications
var scope: Scope { get set }
}
Then you can do this anywhere in your code:
func postNotification() {
let scope: Property.Scope = .all
NotificationCenter.post(name: Property.Notifications.propertyDidChange,
object: scope)
}
Using protocols like Java interfaces is rarely a good idea. They are meta types, meant for defining contracts, which is an entirely different kind of thing.
That being said, just for the point of understanding, I find the most simple and effective way for creating the equivalent of a static factory method of a protocol to write a free function.
It should contain the protocol's name, hoping that that will prevent name clashes, and improve discoverability.
In other languages, createP would be a static member of P, named create and be called as P.create(...), which would drastically improve discoverability and guarantee to prevent name clashes.
In swift, though, this is not an option for protocols, so if protocols are for some reason really actually used as a replacement for interfaces, at least including the protocol's name in the function's name is an ugly workaround that's still slightly better than nothing.
P.S. in case the goal is actually to achieve something like an inheritance hierarchy with structs, union style enums are the tool that's meant to serve that purpose :)
protocol P
{
var x: Int { get }
}
func createP() -> P
{
if (todayIsMonday())
{
return A()
}
else
{
return B()
}
}
class A: P
{
var x = 5
}
class B: P
{
var x = 7
}
This isn't an answer so much as it is an extension to the question. Say I have:
#objc public protocol InteractivelyNameable: Nameable {
static func alertViewForNaming(completion:#escaping((_ success: Bool, _ didCancel: Bool, _ error: Error?) -> Void)) -> UIAlertController?
}
And I have a generic view controller that manages various types (generic type is .fetchableObjectType... basically NSFetchResult). I need to check if a specific object type conforms to the protocol, and if so, invoke it.
something like:
// valid swift code
if self.dataSource.fetchableObjectType is InteractivelyNameable {
// not valid swift code
if let alert = (self.dataSource.fetchableObjectType as InteractivelyNameable).alertViewForNaming(....)
}
I had a situation where I need to create same DomainModel object from 2 different response. so this (static method in protocol helped me) approach helped me.
protocol BaseResponseKeyList: CodingKey {
static func getNameKey()->Self
}
enum FirstResponseKeyList: String, BaseResponseKeyList {
case name
func getNameKey()->FirstResponseKeyList {
return .name
}
}
enum SecondResponseKeyList: String, BaseResponseKeyList {
case userName
func getNameKey()->SecondResponseKeyList {
return .userName
}
}
struct MyDomainModel<T:BaseResponseKeyList> : Decodable {
var name:String?
required init(from d:Decoder) {
do {
let container = try d.container(keyedBy:T.self)
name = try container.decode(String.self, forKey:T.getNameKey())
}catch(_) {
print("error")
}
}
}
let myDomainModel = try JSONDecoder().decode(MyDomainModel <FirstResponseKeyList>.self, from: data)
let myDomainModel2 = try JSONDecoder().decode(MyDomainModel <SecondResponseKeyList>.self, from: data2)

Swift: How to hold any possible instance of a generic type in a variable

The distillation of what I am trying to do is this:
public struct HolderOfWrappers
{
let anyWrappedItem: MyResource<Any>
}
public struct MyResource<A>
{
let wrappedItem : A
let convert: String -> A
}
func holdResource<A>( resource: MyResource<A> ) -> HolderOfWrappers
{
// Error on this line, A is not Any...
let wrapHolder : HolderOfWrappers = HolderOfWrappers( resource )
return wrapHolder
}
As it stands, this code produces the compiler error in the last holdResource method where I'm trying to build a HolderOfWrappers:
Cannot convert the expression's type 'MyResource<A>' to type '(anyWrappedItem: MyResource<Any>)'
Which is understandable as the code indicates HolderOfWrappers can only hold a MyResource built for Any type, not any possible type. What I'm really after with the HolderOfWrappers is something like this:
public struct HolderOfWrappers
{
let anyWrappedItem: MyResource<>
}
or even MyResource<*> - I am trying to say with this code that I'd like a variable that can hold any type of MyResource. If I try to use either syntax though, I get a compiler error that it expects a type.
I could just have anyWrappedItem by of type Any, but then you lose the type information for future use. I also do not want HolderOfWrappers to be generic (because then I'd just have the same problem later).
It's almost like I am trying to treat the generic type as a protocol for the anyWrappedItem storage variable, which will not work for other reasons...
I think you can do what you want by putting a generic parameter in your HolderOfWrappers init method. Basically, the init method just generates a new MyResource using the resource that you provide, like this:
public struct HolderOfWrappers {
let anyWrappedItem: MyResource<Any>
public init<A>(resource: MyResource<A>) {
self.anyWrappedItem = MyResource(wrappedItem: resource.wrappedItem, convert: resource.convert)
}
}
I think that will do what you want. I don't know if it will be slower since you are initializing an entirely new MyResource instead of just copying one.
At any rate, it makes it so that HolderOfWrappers itself is not generic and will fill anyWrappedItem with a MyResource<Any> that holds the same values as the resource that you pass in.
How about this
protocol Wrapper {}
public struct HolderOfWrappers {
let anyWrappedItem: MyResource<Wrapper>
}
public struct MyResource<A> {
let wrappedItem : A
let convert: String -> A
}
func holdResource( resource: MyResource<Wrapper>) -> HolderOfWrappers {
// Error on this line, A is not Any...
let wrapHolder : HolderOfWrappers = HolderOfWrappers(anyWrappedItem: resource)
return wrapHolder
}
of course the downside is you'd have to do extension A: Wrapper { } for any type A that you pass into MyResource.