Swift: How to Hold Any Possible Instance of a Generic Type in a Variable

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

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.

Using a Type Variable in a Generic

Swift's static typing means the type of a variable must be known at compile time.

In the context of a generic function func foo<T>() { ... }, T looks like a variable, but its type is actually known at compile time based on where the function is called from. The behavior of Array<T>() depends on T, but this information is known at compile time.

When using protocols, Swift employs dynamic dispatch, so you can write Array<MyProtocol>(), and the array simply stores references to things which implement MyProtocol — so when you get something out of the array, you have access to all functions/variables/typealiases required by MyProtocol.

But if t is actually a variable of kind Any.Type, Array<t>() is meaningless since its type is actually not known at compile time. (Since Array is a generic struct, the compiler needs know which type to use as the generic parameter, but this is not possible.)

I would recommend watching some videos from WWDC this year:

  • Protocol-Oriented Programming in Swift
  • Building Better Apps with Value Types in Swift

I found this slide particularly helpful for understanding protocols and dynamic dispatch:

Sample Image

Store type in variable to use like a type later

func someFunc<T>(model: T) -> Array<T> {
let array = Array<T>()
return array
}

let someType = SomeModel.self
let array = someFunc(someType())

Looks like this does what I want. The only drawback is that I have to create an instance of the desired type to pass. In this case its minimal overhead, but it just seems like a waste.

Another thing with using generics like this is it appears that the generic's possible types are computed at compile time, so at run time model.dynamicType doesn't necessarily match T. In most cases it will, but if you are doing any reflection driven stuff make sure to really check your use case well.

Swift: Assign an instance of a generic type to a class variable?

Break down the create method signature and the Batch1 type

public func create<Request: JSONRPCKit.Request>(_ request: Request) -> Batch1<Request>

public struct Batch1<Request: JSONRPCKit.Request>: Batch {
public typealias Responses = Request.Response
public typealias Results = Result<Request.Response, JSONRPCError>
}

create is a generic function that takes a single parameter of any type. The constraint <Request: JSONRPCKit.Request> specifies that the parameter's type must conform to the protocol JSONRPCKit.Request.

Batch1 is a generic struct that needs to define two internal types, both of which are associated with some arbitrary type. Again, <Request: JSONRPCKit.Request> specifies that that arbitrary type must conform to the protocol JSONRPCKit.Request.

The return type Batch1<Request> ties these two generics together, saying that the type used for the returned Batch1 struct will match the type of the request parameter.

When you call the create method, you must fill in the generic type with a concrete type, as in the example

let batch = batchFactory.create(Subtract(minuend: 42, subtrahend: 23))

Now the compiler can go through all of the definitions and create concrete implementations for that type:

public func create(_ request: Subtract) -> Batch1<Subtract>

public struct Batch1<Subtract>: Batch {
public typealias Responses = Int
public typealias Results = Result<Int, JSONRPCError>
}

This uses the fact that Subtract defines typealias Response = Int. Note that nothing is generic anymore; these are all concrete types. You would have no issue with trying to store a property of type Batch1<Subtract>.


This is why you can't easily store the batch in a property: Swift has no idea what types to put in it!

One way around this is to instead store a closure, this can wrap the generic batch such that the class doesn't have to know about it

// closure property
var responseProcessor: ((Any) -> Void)?

func createBatch<R: JSONRPCKit.Request>(request: R, processor: @escaping (R.Response) -> Void) {
let batch = batchFactory.create(request)
self.responseProcessor = { responseObject in
let response = try! batch.responses(from: responseObject)
processor(response)
}
}

// somewhere else when you get the responseObject
responseProcessor?(responseObject)

This method takes a specific closure that matches the generic type and wraps it in a closure that is no longer dependent on the generic. This way every batch can share the same closure property.

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<Int>. 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<S: StoringType>(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.

Swift: Reference to a generic class

I finally succeed doing what i wanted!

Not perfect at all, feel free to comment for architecture improvements (especially on the asBaseProtocol() part...)

Here's my complete code (Swift 3.0)

DataFilter

protocol DataFilterDelegate : class {
func didFilter()
func didUpdateItems()
}

class DataFilter<T> {
public weak var delegate : DataFilterDelegate?

private var items : [SelectableItem<T>]?
private var filteredItems : [SelectableItem<T>]?
var source: [SelectableItem<T>]? {
get {
if filteredItems != nil {
return filteredItems
}
return items
}
}

var filter : (T,String) -> Bool
var populateCell : (T) -> UITableViewCell

init(filter : @escaping (T,String) -> Bool, populateCell: @escaping (T) -> UITableViewCell) {
self.filter = filter
self.populateCell = populateCell
}

func updateItems(_ items: [T]) {
self.items = [SelectableItem<T>]()
for item in items {
self.items?.append(SelectableItem(item))
}
delegate?.didUpdateItems()
}

func filterItems(text : String) {
filteredItems = (text == "") ? nil : items?.filter { item in
filter(item.item, text)
}
delegate?.didFilter()
}

func selectedItems() -> [T]? {
guard let items = items else {
return nil
}
var selectedItems = [T]()
for item in items {
if item.isSelected {
selectedItems.append(item.item)
}
}
return selectedItems
}
}

extension DataFilter where T : FIRDataObject {
func asBaseProtocol() -> DataFilter<FIRDataObject> {
return DataFilter<FIRDataObject>(filter: filterAsBaseProtocol(), populateCell: populateCellAsBaseProtocol())
}

private func filterAsBaseProtocol() -> ((FIRDataObject,String) -> Bool) {
return { (object, text) -> Bool in
self.filter(object as! T, text)
}
}

private func populateCellAsBaseProtocol() -> ((FIRDataObject) -> UITableViewCell) {
return { (object) -> UITableViewCell in
self.populateCell(object as! T)
}
}
}

ParentViewController Class

class ParentViewController : UIViewController {
public var dataFilter : DataFilter<FIRDataObject>? {
didSet {
dataFilter!.delegate = self
}
}

// Some Functions using dataFilter
}

ChildViewController Class

class ChildViewController : Parent {

// Stored as a variable to not have to cast objects to the good type everytime I access dataFilter
private var patientDataFilter = DataFilter<Patient>(filter: { patient, text in
patient.firstname.contains(text) ||
patient.lastname.contains(text)
}
, populateCell: { patient in
let cell = UITableViewCell(style: .subtitle, reuseIdentifier: "Patient")
cell.textLabel?.text = patient.lastname + " " + patient.firstname
cell.detailTextLabel?.text = patient.postalCode + " " + patient.city
return cell
})

override func viewDidLoad() {
super.viewDidLoad()

dataFilter = patientDataFilter.asBaseProtocol()
}

func someFunc() {
let patient1 = patientDataFilter.source[0].item
// OR
let patient2 = dataFilter.source[0].item as! Patient
}
}

How to store generic type to avoid internal struct requiring it in Swift?

Here is possible approach (however I'd keep it outside, like ButtonStyle is outside of Button)... anyway, here it is:

public struct Example {
private let content: AnyView

init<Content: View>(@ViewBuilder content: @escaping () -> Content) {
self.content = AnyView(content())
}

func contentView() -> some View {
self.content
}

public struct ActionKey: Hashable, Equatable, RawRepresentable {
public static let cancelButtonClicked = ActionKey("cancelButtonClicked") // Static stored properties not supported in generic types

public static func == (lhs: ActionKey, rhs: ActionKey) -> Bool {
return lhs.rawValue == rhs.rawValue
}

public let rawValue: String

public init(rawValue: String) {
self.rawValue = rawValue
}

public init(_ rawValue: String) {
self.init(rawValue: rawValue)
}
}
}

How do I store a value of type ClassClassImplementingProtocol in a Dictionary of type [String:ClassProtocol] in Swift?

A Thing<Vanilla> is not a Thing<Flavor>. Thing is not covariant. There is no way in Swift to express that Thing is covariant. There are good reasons for this. If what you were asking for were allowed without careful rules around it, I would be allowed to write the following code:

func addElement(array: inout [Any], object: Any) {
array.append(object)
}

var intArray: [Int] = [1]
addElement(array: &intArray, object: "Stuff")

Int is a subtype of Any, so if [Int] were a subtype of [Any], I could use this function to append strings to an int array. That breaks the type system. Don't do that.

Depending on your exact situation, there are two solutions. If it is a value type, then repackage it:

let thing = Thing<Vanilla>(value: Vanilla())
dict["foo"] = Thing(value: thing.value)

If it is a reference type, box it with a type eraser. For example:

// struct unless you have to make this a class to fit into the system, 
// but then it may be a bit more complicated
struct AnyThing {
let _value: () -> Flavor
var value: Flavor { return _value() }
init<T: Flavor>(thing: Thing<T>) {
_value = { return thing.value }
}
}

var dict = [String:AnyThing]()
dict["foo"] = AnyThing(thing: Thing<Vanilla>(value: Vanilla()))

The specifics of the type eraser may be different depending on your underlying type.


BTW: The diagnostics around this have gotten pretty good. If you try to call my addElement above in Xcode 9, you get this:

Cannot pass immutable value as inout argument: implicit conversion from '[Int]' to '[Any]' requires a temporary

What this is telling you is that Swift is willing to pass [Int] where you ask for [Any] as a special-case for Arrays (though this special treatment isn't extended to other generic types). But it will only allow it by making a temporary (immutable) copy of the array. (This is another example where it can be hard to reason about Swift performance. In situations that look like "casting" in other languages, Swift might make a copy. Or it might not. It's hard to be certain.)

Dictionary with String as Key and Generic Class as value

Create a base class (let's call it BasePlug) that all Plug classes will derive from. Have your dictionary store items of class BasePlug:

class BasePlug {

}

class AClass {

}

class BClass {

}

class Plug<T> : BasePlug {

}

class Socket {
var plugs = [ String: BasePlug ]() // should accept any Plug
init() {

}
func addPlug( plug : BasePlug ) { // should accept any Plug
self.plugs["A"] = plug // should accept any Plug
}
}

var plugDouble = Plug<Double>()
var plugString = Plug<String>()
var plugAClass = Plug<AClass>()
var plugBClass = Plug<BClass>()

var socket = Socket()
socket.addPlug(plugDouble ); // should accept any Plug
socket.plugs["A"] = plugDouble // should accept any Plug


Related Topics



Leave a reply



Submit