How to Use Generic Protocol as a Variable Type

How to use generic protocol as a variable type

As Thomas points out, you can declare your variable by not giving a type at all (or you could explicitly give it as type Printer. But here's an explanation of why you can't have a type of the Printable protocol.

You can't treat protocols with associated types like regular protocols and declare them as standalone variable types. To think about why, consider this scenario. Suppose you declared a protocol for storing some arbitrary type and then fetching it back:

// a general protocol that allows for storing and retrieving
// a specific type (as defined by a Stored typealias
protocol StoringType {
typealias Stored

init(_ value: Stored)
func getStored() -> Stored
}

// An implementation that stores Ints
struct IntStorer: StoringType {
typealias Stored = Int
private let _stored: Int
init(_ value: Int) { _stored = value }
func getStored() -> Int { return _stored }
}

// An implementation that stores Strings
struct StringStorer: StoringType {
typealias Stored = String
private let _stored: String
init(_ value: String) { _stored = value }
func getStored() -> String { return _stored }
}

let intStorer = IntStorer(5)
intStorer.getStored() // returns 5

let stringStorer = StringStorer("five")
stringStorer.getStored() // returns "five"

OK, so far so good.

Now, the main reason you would have a type of a variable be a protocol a type implements, rather than the actual type, is so that you can assign different kinds of object that all conform to that protocol to the same variable, and get polymorphic behavior at runtime depending on what the object actually is.

But you can't do this if the protocol has an associated type. How would the following code work in practice?

// as you've seen this won't compile because
// StoringType has an associated type.

// randomly assign either a string or int storer to someStorer:
var someStorer: StoringType =
arc4random()%2 == 0 ? intStorer : stringStorer

let x = someStorer.getStored()

In the above code, what would the type of x be? An Int? Or a String? In Swift, all types must be fixed at compile time. A function cannot dynamically shift from returning one type to another based on factors determined at runtime.

Instead, you can only use StoredType as a generic constraint. Suppose you wanted to print out any kind of stored type. You could write a function like this:

func printStoredValue(storer: S) {
let x = storer.getStored()
println(x)
}

printStoredValue(intStorer)
printStoredValue(stringStorer)

This is OK, because at compile time, it's as if the compiler writes out two versions of printStoredValue: one for Ints, and one for Strings. Within those two versions, x is known to be of a specific type.

Use generic protocols as variable type

While this is closely related to the question David Smith linked (and you should read that as well), it's worth answering separately because it's a different concrete use case, and so we can talk about it.

First, imagine you could store this variable. What would you do with it? What function in NetworkManager could call delegate.didFinishFetchingData? How would you generate the ResultData when you don't know what it is?

The point is that this isn't what PATs (protocols with associated types) are for. It's not their goal. Their goal is to help you add extensions to other types, or to restrict which kinds of types can be passed to generic algorithms. For those purposes, they're incredibly powerful. But what you want are generics, not protocols.

Instead of creating delegates, you should use generic functions to handle the result of a specific call, rather than trying to nail down each view controller to a specific result type (which isn't very flexible anyway). For example, in the simplest, and least flexible way that still gives you progress reporting:

struct APIClient {
func fetch(_: Model.Type,
with urlRequest: URLRequest,
completion: @escaping (Result) -> Void)
-> Progress {

let session = URLSession.shared

let task = session.dataTask(with: urlRequest) { (data, _, error) in
if let error = error {
completion(.failure(error))
}
else if let data = data {
let decoder = JSONDecoder()
completion(Result {
try decoder.decode(Model.self, from: data)
})
}
}
task.resume()
return task.progress
}
}

let progress = APIClient().fetch(User.self, with: urlRequest) { user in ... }

This structure is the basic approach to this entire class of problem. It can be made much, much more flexible depending on your specific needs.

(If your didStartFetchingData method is very important, in ways that Progress doesn't solve, leave a comment and I'll show how to implement that kind of thing. It's not difficult, but this answer is pretty long already.)

Swift 5: how to specify a generic type conforming to protocol when declaring a variable

An associated type is used when you want your protocol to work with a variety of types, think a Container protocol that might have several methods all dealing with one contained type.

But your protocol is not that, it doesn't need to know any other types to specify the necessary behavior, so get rid of the associated type.

protocol Pipe {
func await() -> Void
func yield( to: Any, with listener: Selector ) -> Void
}

class Foo {
var imageSource: Pipe & Renderable
}

Variable that conforms to a protocol that has a generic function

Swift doesn't allow this.

Here's why: you don't know anything about the type of argument person.forward(_:) takes. There is no way to call it. MyProtocol essentially defines an open-ended set of independent types.

If you don't want to be able to call person.forward(_:), and you just want to be able to access the non-generic person.name property, then split your protocol into a base, non-generic protocol defining name, and a sub-protocol that adds the generic forward(_:) method.

protocol NamedThing {
var name: String {get set}
}

protocol MovableNamedThing: NamedThing {
associatedtype SpeedType
func forward(_: SpeedType)
}

class A: MovableNamedThing {
typealias SpeedType = Double
var name: String

init(name:String) {
self.name = name
}

func forward(_ s: Double) {
print("Moving \(s) km/h")
}
}

class B: MovableNamedThing {
typealias SpeedType = Int
var name: String

init(name:String) {
self.name = name
}

func forward(_ s: Int) {
print("Moving \(s) km/h")
}
}

let x: Bool = true
var person: NamedThing
if x {
person = A(name: "Robot")
} else {
person = B(name: "Human")
}

Is there any way to use the generic protocol as a data type in a function?

As error stated, T is a generic parameter whose class is not defined in function declaration because T is associated with ICRUDOperation so you should use your delegate as:

func Delegate1(sqlite: W, service: W, data: W.T) {
sqlite.insert(data: data)
}

Generics Protocol as Type in Swift

I've solved this from few months ago with descriptions below. Please check it and give me another solution if you have.

Firstly, make some change for Protocol. At associatedtype T should be change to associatedtype Component and Component is a class which will be inherited from another class (important step).

public protocol ProComponentFactory {
associatedtype Component
func create() -> Component?
}

Second, I will make a Generic Struct with inheritance from ProComponentFactory:

public struct ComponentFactory: ProComponentFactory {
public typealias Component = T
public func create() -> T? { return T.self as? T }
}

Well done, for now you can define a variable as I example in my question above:

fileprivate var mComponentFactoryMap = Dictionary>()

As well for any class was inherit from Component and variable mComponentFactoryMap can using extension inside.



Related Topics



Leave a reply



Submit