Swift Generics - Returning Subclasses of Generic Class - swift

I'm looking to return any number of subclasses of a generic class in a single method but am unsure of how to format the function in order to do so. Below is a sample playground of my issue.
import UIKit
class TableViewCellData {
}
class SubTableViewCellData: TableViewCellData {
}
protocol ClassProtocol {
associatedtype DataType: TableViewCellData
}
class SuperDataClass {
}
class SubDataClass: SuperDataClass {
}
class SuperClass<T: SuperDataClass>: UITableViewCell, ClassProtocol {
typealias DataType = TableViewCellData
}
class SubClass: SuperClass<SubDataClass> {
typealias DataType = SubTableViewCellData
}
class Example {
func test<T: SuperDataClass>(object: SuperClass<T>) {
print(object)
}
func returnType() -> SuperClass<SuperDataClass>.Type? {
var objectToReturn: SuperClass<SuperDataClass>.Type?
if true {
objectToReturn = SuperClass<SuperDataClass>.self
} else {
objectToReturn = SubClass.self
}
return objectToReturn
}
}
let object = SubClass()
Example().test(object: object)
if let object2 = Example().returnType() {
print(object2)
}
The code above will not run in a playground, you will see an error pointing to line 42 objectToReturn = SubClass.self because error: cannot assign value of type 'SubClass.Type' to type 'SuperClass<SuperDataClass>.Type?'. Is there a way to change the object type to expect the generic and any subclass version of the generic?

Related

Cannot assign a value of type T to type T

I am trying to create a reusable ViewController accepting a generic type. But I get this error:
Cannot assign value of type 'LevelType' to type 'LevelType?'
import Foundation
import UIKit
protocol SomeProtocol {
var id: Int { get }
}
protocol VCProtocol: class {
func showLevelScreen<LevelType>(level: LevelType)
}
class SomeVC<LevelType: SomeProtocol>: UIViewController, VCProtocol {
typealias LevelType = LevelType
var selectedLevel: LevelType? //Cannot assign value of type 'LevelType' to type 'LevelType?'
func showLevelScreen<LevelType>(level: LevelType) {
selectedLevel = level
}
}
Any idea how to get around this?
No complaints from the compiler when using this:
class SomeVC<T: SomeProtocol>: UIViewController, VCProtocol {
var selectedLevel: T?
func showLevelScreen<LevelType: SomeProtocol>(level: LevelType) {
selectedLevel = level as? T
}
}
remove generic type in your function
func showLevelScreen(level: LevelType) {
selectedLevel = level
}

Generics in protocols

I have a UnitDimension class given by:
class UnitDimension: {
var symbol: String
init(symbol: String) {
self.symbol = symbol
}
}
and a UnitVolume subclass of this class:
class UnitVolume: UnitDimension {
static let liter = UnitVolume(symbol: "L")
}
I want to have a protocol UnitDimensionHandler that allows me to perform some simple functions. Firstly it must have an allUnits variable:
protocol UnitDimensionHandler: class {
static var allUnits: [UnitDimension] { get }
}
Is it possible to have allUnits a generic type of array that must be a subclass of UnitDimension? I could then implement it in UnitVolume as follows:
extension UnitVolume: UnitDimensionHandler {
static var allUnits: [UnitVolume] {
return [liter]
}
}
I then want to include a function that also allows me to use a generic subclass type that initiates a UnitDimension instance:
extension UnitDimensionHandler {
static func unit(for symbol: String) -> UnitDimension? {
return allUnits.first() { $0.symbol == symbol }
}
}
such that UnitVolume.unit(for: "L") returns an optional UnitVolume rather than an optional UnitDimension.
Thanks for any help.
Yes it is possible, using associatedtype:
protocol UnitDimensionHandler: class {
// here we define `generic` type variable `Dimension`, and specify it do be descendant of `UnitDimension`
associatedtype Dimension: UnitDimension
// and use it here, instead of `UnitDimension`
static var allUnits: [Dimension] { get }
}
And
extension UnitDimensionHandler {
// same `UnitDimension` -> `Dimension` replacement
static func unit(for symbol: String) -> Dimension? {
return allUnits.first() { $0.symbol == symbol }
}
}

Issue with generics, associated types and equatable

I've made a dummy project to test generics and associated types.
Here is a protocol
protocol WordProto : Equatable { // BTW not sure if I should put Equatable here
associatedtype WordAlias : Equatable // or here
var homonyms: [WordAlias] { get }
}
And here is a class
class SomeFrameworkClass<T : WordProto> {
typealias SomeWord = T
func testClass(word: SomeWord) {
if word.homonyms.contains(word) {
}
}
}
So this doesn't compile on the contains and here is the error:
Cannot invoke contains with an argument list of type T
Not sure how to solve this, thanks for your help!
T and WordAlias could be different types. This should be specified.
IMHO this should work:
protocol WordProto {
associatedtype WordAlias: Equatable
var homonyms: [WordAlias] { get }
}
class SomeFrameworkClass<T: WordProto> where T.WordAlias == T {
func testClass(word: T) {
if word.homonyms.contains(word) {
}
}
}
OR with self reference:
protocol WordProto: Equatable {
var homonyms: [Self] { get }
}
class SomeFrameworkClass<T: WordProto> {
func testClass(word: T) {
if word.homonyms.contains(word) {
}
}
}

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)
}

How do I make a referential type comparison in Swift using 'is'?

I can't figure out how to make a type comparison in Swift using the is operator, if the right side is a reference and not a hard-coded type.
For example,
class GmBuilding { }
class GmOffice: GmBuilding { }
class GmFactory: GmBuilding { }
class GmStreet {
var buildings: [GmBuilding] = []
func findAllBuildingsOfType(buildingType: GmBuilding.Type) -> [GmBuilding] {
var result: [GmBuilding] = []
for building in self.buildings {
if building is buildingType { // complains that buildingType is not a type
result.append(building)
}
}
return result
}
}
let myStreet = GmStreet()
var buildingList: [GmBuilding] = myStreet.findAllBuildingsOfType(GmOffice.self)
It complains that 'buildingType is not a type'. How can it be made to work?
A generic method may do what you want:
func findAllBuildingsOfType<T: GmBuilding>(buildingType: T.Type) -> [GmBuilding] {
// you can use `filter` instead of var/for/append
return buildings.filter { $0 is T }
}
This will work so long as you really do only want to determine the type at compile time:
let myStreet = GmStreet()
let buildingList = myStreet.findAllBuildingsOfType(GmOffice.self)
// T is set at compile time to GmOffice --------^
However, often when this question comes up, the follow-up question is, how do I store GmOffice.self in a variable and then have the type be determined at runtime? And that will not work with this technique. But if statically fixed types at compile time are enough for you, this should do it.
If AirSpeed Velocity's answer doesn't work for you, you can also accomplish this by bridging to Objective-C.
Make GmBuilding inherit from NSObject:
class GmBuilding: NSObject { }
And use isKindOfClass(_:) to check the type:
for building in self.buildings {
if building.isKindOfClass(buildingType) {
result.append(building)
}
}
Not as Swifty, but it works.
I'm sure there must be a better way than this, but it doesn't require inheritance from NSObject and it works at runtime - according to my playground
class GmBuilding { }
class GmOffice: GmBuilding { }
class GmFactory: GmBuilding { }
func thingIs(thing: GmBuilding, #sameTypeAs: GmBuilding) -> Bool
{
return thing.dynamicType === sameTypeAs.dynamicType
}
var foo: GmOffice = GmOffice()
thingIs(foo, sameTypeAs: GmOffice()) // true
thingIs(foo, sameTypeAs: GmFactory()) // false
The main reason I instantiate an object (you can use a singleton instead) is because I can't figure out how to declare a parameter to be a metatype.
It also doesn't work for
thingIs(foo, sameTypeAs: GmBuilding()) // false :=(
As a final resort, using Obj-C reflect function:
import ObjectiveC
func isinstance(instance: AnyObject, cls: AnyClass) -> Bool {
var c: AnyClass? = instance.dynamicType
do {
if c === cls {
return true
}
c = class_getSuperclass(c)
} while c != nil
return false
}
class GmBuilding { }
class GmOffice: GmBuilding { }
class GmFactory: GmBuilding { }
isinstance(GmOffice(), GmOffice.self) // -> true
isinstance(GmOffice(), GmFactory.self) // -> false
isinstance(GmOffice(), GmBuilding.self) // -> true