Swift, use generic property with different specific types - Reference to generic type requires arguments in - swift

How would one go about assigning an instance of a class which can have a generic type, but you don't know what type that is until run time?
For example.
We have a protocol and enums that conform to it like this:
protocol Stage: CaseIterable, Hashable {
var fooBarLength: Int { get }
}
enum FirstStage: String, Stage {
var fooBarLength: Int { 10 }
case section1
case section2
}
enum SecondStage: String, Stage {
var fooBarLength: Int { 10 }
case section1
case section2
case section3
}
Next we have some kind of controller that uses the protocol as a generic type... comme ça...
class FooBarController<StageType: Stage>: UIViewController {
private var stages: [StageType: Float] = [:]
}
Then used like this:
func fooBarScreen(boop: SomethingThatKnowsAboutTheStages) {
var fooBarController: FooBarController // <---- how do I define this????
if boop.someCondition() {
fooBarController = FooBarController<FirstStage>()
} else {
fooBarController = FooBarController<SecondStage>()
}
}
In Java / Kotlin I could just do this as it is above, how do I achieve the same thing in Swift?
Currently I get
"Reference to generic type 'FooBarController' requires arguments in <...>"
Secondary Question
Is there a more generic way than having to use that if-statement here? Ideally I would like the fooBarScreen method to not care about the generic type and just have SomethingThatKnowsAboutTheStages provide the type for me.

You can specify a protocol for providing Stage types like so:
protocol StageProvider {
associatedtype T: Stage
func getType() -> T.Type
}
Then make your SomethingThatKnowsAboutTheStages or any other one conform this protocol:
class SomethingThatKnowsAboutTheStages: StageProvider {
typealias T = SecondStage
func getType() -> T.Type {
SecondStage.self
}
}
Add an initializer for your FooBarController:
class FooBarController<StageType: Stage>: UIViewController {
convenience init(stage: StageType.Type) {
self.init()
}
}
And finally use all these:
func fooBarScreen<T: StageProvider>(boop: T) {
let controller = FooBarController(stage: boop.getType())
}

Related

swift generic constraint type is also a return type

protocol Builder {
associatedtype Output where Output: MyProtocol
func build() -> Output?
}
// concrete type
struct ABuilder: Builder {
func builder() -> MyProtocol {
if someCondition {
return aSubClassOfMyProtocol
} else {
return anotherSubClassOfMyProtocol
}
}
}
MyProtocol is a protocol type. It is also the Output constraint. Because the concrete Builder ABuilder is going to return two different sub class types that conform MyProtocol. How can I make the generic constraint work?
I am trying to make the generic constraint be the same.
You can make build() function generic or use typecasting. Anyway this two ways determine concrete type outside of method not inside.
So first of all you need to remove associatedtype. If you specify associated type Output it means that the implementation of your Builder protocol should return some concrete type which conforms to MyProtocol. If you want your Builder to return any type which conforms to MyProtocol not the concrete one specified for this implementation of Builder then you shouldn't declare any associated types.
Let's see how type casting can work:
protocol Builder {
func build() -> MyProtocol?
}
struct ABuilder: Builder {
func build() -> MyProtocol? {
// some code
}
}
let builder = ABuilder()
guard let a = builder.build() as? SomeClass else { return }
Or we can use generic approach:
protocol Builder {
func build<T: MyProtocol>() -> T?
}
struct ABuilder: Builder {
func build<T: MyProtocol>() -> T? {
// some code
}
}
let builder = ABuilder()
let a: SomeClass = builder.build()
You can read more about that here, here and here. Moreover I strongly recommend watching this video which describes all the limitations of generics in swift.
I think you are correct that having the typecasting outside the Builder is a better way. I also found another possibility. The difference is that we have the concrete class types from the build function.
protocol MyProtocol { }
protocol Builder {
associatedtype Output
func build() -> Output?
}
struct ASubClassOfMyProtocol: MyProtocol {}
struct AnotherSubClassOfMyProtocol: MyProtocol {}
struct ABuilder: Builder {
func build() -> (any MyProtocol)? {
if Int.random(in: 0...10) < 5 {
return ASubClassOfMyProtocol()
} else {
return AnotherSubClassOfMyProtocol()
}
}
}

Use generics in place of a dedicated variable enum

I have a protocol. This is implemented by many structs that fall into one of two types of category: TypeOne and TypeTwo. I want to be able to distinguish between their types, so I've added an enum ProtocolType that defines the types typeOne and typeTwo. By default I set the protocolType to be typeOne, but I manually specify typeTwo when it's a TypeTwo struct:
enum ProtocolType {
case typeOne
case typeTwo
}
protocol MyProtocol {
let name: String { get }
var protocolType: ProtocolType { get }
}
extension MyProtocol {
var protocolType: ProtocolType {
return .typeOne
}
}
enum TypeOne {
struct Foo: MyProtocol {
let name = "foo"
}
}
enum TypeTwo {
struct Bar: MyProtocol {
let name = "bar"
let protocolType = .typeTwo
}
}
Is there any way I can remove the necessity for defining protocolType in all structs and somehow use generics to identify what type a struct is? They're already defined under the TypeOne and TypeTwo convenience enums, I was wondering if I could utilise that some how?
Given some protocol:
protocol MyProtocol {
var name: String { get }
}
It sounds like you want to "tag" certain types as special, even though they have the same requirements. That's not an enum, that's just another type (protocol):
// No additional requirements
protocol SpecialVersionOfMyProtocol: MyProtocol {}
You can then tag these at the type level, not the value level:
struct Foo: MyProtocol {
let name = "foo"
}
struct Bar: SpecialVersionOfMyProtocol {
let name = "bar"
}
And you can tell the difference using is if you need to:
func f<T: MyProtocol>(x: T) {
if x is SpecialVersionOfMyProtocol {
print("special one")
}
}
In most cases, though, I wouldn't use this kind of runtime check. I'd just have two protocols (one for TypeOne and one for TypeTwo), and implement whatever you need as extensions on those. For example, say you want to print the name differently depending on the type. Start with a protocol that just expresses that:
protocol NamePrintable {
var name: String { get }
func printName()
}
func printIt<T: NamePrintable>(x: T) {
x.printName()
}
Then extend that for TypeOnes and TypeTwos:
protocol TypeOne: NamePrintable {}
extension TypeOne {
func printName() {
print("I'm a type one with the name \(name)")
}
}
protocol TypeTwo: NamePrintable {}
extension TypeTwo {
func printName() {
print("I'm a type two with the name \(name)")
}
}
And conform your structs:
struct Foo: TypeOne {
let name = "foo"
}
struct Bar: TypeTwo {
let name = "bar"
}
printIt(x: Foo()) // I'm a type one with the name foo
printIt(x: Bar()) // I'm a type two with the name bar
If you want a default implementation, you can hang it on NamePrintable, but I kind of recommend not doing that for what you've described. I'd probably just have "type one" and "type two" explicitly.
extension NamePrintable {
func printName() {
print("BASE FUNCTIONALITY")
}
}

Syntactic help: constraining functions to generic class

I have a structure that I simplified like this:
protocol Protocol {
associatedtype T
var common: T { get }
}
class Superclass<T>: Protocol {
let common: T
init(common: T) { self.common = common }
}
class IntClass<T>: Superclass<T> {
let int = 5
}
class StringClass<T>: Superclass<T> {
let string = "String"
}
class Example<P: Protocol> {
let object: P
init(object: P) { self.object = object }
func common() -> P.T { object.common }
func int() -> Int where P == IntClass<Any> { object.int }
func string() -> String where P == StringClass<Any> { object.string }
}
I would like to create objects of the generic class Example where some of them contain an object of the also generic IntClass while others have a generic StringClass object. Now I’d like to add accessors on Example for IntClass and StringClass specific properties (so I don’t have to access them directly). They would need to be constrained to the respective class. These would be int() and string() in my example.
My example doesn’t work like intended though:
let intExample = Example(object: IntClass(common: Double(1)))
// 👍 (= expected)
intExample.common() // Double 1
// 👍 (= expected)
intExample.string() // Instance method 'string()' requires the types 'IntClass<Float>' and 'StringClass<Any>' be equivalent
// 👎 (= not expected)
intExample.int() // Instance method 'int()' requires the types 'IntClass<Float>' and 'IntClass<Any>' be equivalent
I also tried:
func int() -> Int where P == IntClass<P.T> { object.int }
With these compiler complaints:
- Generic class 'Example' requires that 'P' conform to 'Protocol'
- Same-type constraint 'P' == 'IntClass<P.T>' is recursive
- Value of type 'P' has no member 'int'
And I tried:
func string<T>() -> String where P == StringClass<T> { object.string }
which, when using like intExample.string() results in Generic parameter 'T' could not be inferred (next to Instance method 'string()' requires the types 'IntClass<Double>' and 'StringClass<T>' be equivalent).
I don’t want string() to appear on an Example<IntClass> object in code completion.
Is there a syntax to accomplish what I want (anything with typealias?) or would I have to navigate around that problem?
Since the properties you are trying to access here doesn't depend on the type parameter T of IntClass or StringClass, you can write two non generic protocols HasInt and HasString:
protocol HasInt {
var int: Int { get }
}
protocol HasString {
var string: String { get }
}
extension IntClass: HasInt { }
extension StringClass: HasString { }
Then you can constrain to the protocols, and access int and string through the protocol instead:
func int() -> Int where P: HasInt { object.int }
func string() -> String where P: HasString { object.string }

Is is possible to create generic computed properties with Self or associated type requirements in Swift, and if so how?

Consider the following:
protocol SomeProtocol: Equatable {}
// then, elsewhere...
var someValue: Any?
func setSomething<T>(_ value: T) where T: SomeProtocol {
someValue = value
}
func getSomething<T>() -> T? where T: SomeProtocol {
return someValue as? T
}
These functions work fine but essentially act like computed properties. Is there any way to implement something like the following?
var something<T>: T where T: SomeProtocol {
get { return someValue as? T }
set { someValue = newValue }
}
Thank you for reading. Apologies if this question has already been asked elsewhere, I have searched but sometimes my search fu is weak.
You need to define the computed property on a generic type, the computed property itself cannot define a generic type parameter.
struct Some<T:SomeProtocol> {
var someValue:Any
var something:T? {
get {
return someValue as? T
}
set {
someValue = newValue
}
}
}

Is there some workaround to cast to a generic base class without knowing what the defined element type is?

I am trying to achieve a design where I can have a base class that has a generic property that I can change values on by conforming to a protocol.
protocol EnumProtocol {
static var startValue: Self { get }
func nextValue() -> Self
}
enum FooState: EnumProtocol {
case foo1, foo2
static var startValue: FooState { return .foo1 }
func nextValue() -> FooState {
switch self {
case .foo1:
return .foo2
case .foo2:
return .foo1
}
}
}
enum BarState: EnumProtocol {
case bar
static var startValue: BarState { return .bar }
func nextValue() -> BarState {
return .bar
}
}
class BaseClass<T: EnumProtocol> {
var state = T.startValue
}
class FooClass: BaseClass<FooState> {
}
class BarClass: BaseClass<BarState> {
}
Is it possible to end up with a solution similar to this where the element type is unknown and the value relies on the nextValue() method.
let foo = FooClass()
let bar = BarClass()
if let test = bar as? BaseClass {
test.state = test.state.nextValue()
}
This works but BarState will be unknown in my case and a lot of classes will be subclasses of BaseClass and have different state types.
let bar = BarClass()
if let test = bar as? BaseClass<BarState> {
test.state = test.state.nextValue()
}
This is a simplified example. In my case I will get a SKNode subclass that has a state property that is an enum with a nextvalue method that have defined rules to decide what the next value will be. I am trying to have a generic implementation of this that only relies on what is returned from the nextValue method. Is there a better pattern to achieve this?
This will not work for this exact scenario because EnumProtocol can not be used as concrete type since it has a Self type requirement, however, in order to achieve this type of behavior in other cases you can create a protocol that the base class conforms to and try to cast objects to that type when you are trying to determine if an object is some subclass of that type.
Consider the following example
class Bitcoin { }
class Ethereum { }
class Wallet<T> {
var usdValue: Double = 0
}
class BitcoinWallet: Wallet<Bitcoin> {
}
class EthereumWallet: Wallet<Ethereum> {
}
let bitcoinWallet = BitcoinWallet() as Any
if let wallet = bitcoinWallet as? Wallet {
print(wallet.usdValue)
}
This will not work, due to the same error that you are referring to:
error: generic parameter 'T' could not be inferred in cast to 'Wallet<_>'
However, if you add the following protocol
protocol WalletType {
var usdValue: Double { get set }
}
and make Wallet conform to that
class Wallet<T>: WalletType
then you can cast values to that protocol and use it as expected:
if let wallet = bitcoinWallet as? WalletType {
print(wallet.usdValue)
}