Swift Generics Not Preserving Type

Swift generics not preserving type

This code works as expected.

class BaseClass {

required init() {} // <-- ADDED THIS

func printme() -> Void {
println("I am BaseClass")
}
}

class DerivedClass : BaseClass {
override func printme() -> Void {
println("I am DerivedClass")
}
}

class Util<T: BaseClass> {
func doSomething() {
var instance = T()
instance.printme()
}
}

var util = Util<DerivedClass>()
util.doSomething()

Code base are stolen from @GregoryHigley answer :)

Marking init() {} as required did the thing.
This guarantees init() is the designated initializer of ANY derived class from BaseClass.

Without it, one can make illegal subclass like:

class IllegalDerivedClass : BaseClass {
var name:String

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

override func printme() -> Void {
println("I am DerivedClass")
}
}

var util = Util<IllegalDerivedClass>()
util.doSomething()

You know this doesn't work because IllegalDerivedClass doesn't inherit init() initializer.

I think, that is the reason of your problem.

Anyway, whose fault is that?

  • Compiler should warn about ambiguousness.
  • Runtime should try to initialize DerivedClass() as specified with T.
  • Debugger should show instance is a instance of BaseClass as it actually is.

ADDED:

As of Xcode 6.1 GM 2, It seems, you need more work. (in addition to required init() {})

class Util<T: BaseClass> {
let theClass = T.self // store type itself to variable

func doSomething() {
var instance = theClass() // then initialize
instance.printme()
}
}

I have absolutely no idea why we need this, what's going on X(

ADDED:2014/10/18

I found this also works:

    func doSomething() {
var instance = (T.self as T.Type)()
instance.printme()
}

ADDED: 2015/02/10

As of Xcode Version 6.3 (6D520o) / Swift 1.2

We no longer need (T.self as T.Type)() hack. Just T() works as long as T has required init() initializer.

class Util<T: BaseClass> {
func doSomething() {
var instance = T()
instance.printme()
}
}

Generic type not preserved when called within another generic function

You're trying to reinvent class inheritance with generics. That is not what generics are for, and they don't work that way. Generic methods are statically dispatched, which means that the code is chosen at compile-time, not runtime. An overload should never change the behavior of the function (which is what you're trying to do here). Overrides in where clauses can be used to improve performance, but they cannot be used to create dynamic (runtime) dispatch.

If you must use inheritance, then you must use classes. That said, the problem you've described is better solved with a generic Task rather than a protocol. For example:

struct Task<Result> {
let execute: () throws -> Result
}

enum TaskRunner {
static func run<Result>(task: Task<Result>) throws -> Result {
try task.execute()
}
}

let specificTask = Task(execute: { "Some Result" })

print(try TaskRunner.run(task: specificTask)) // Prints "Some Result"

Notice how this eliminates the "task not supported" case. Rather than being a runtime error, it is now a compile-time error. You can no longer call this incorrectly, so you don't have to check for that case.

If you really want dynamic dispatch, it is possible, but you must implement it as dynamic dispatch, not overloads.

enum TaskRunner<T: Task> {
static func run(task: T) throws -> T.Result {

switch task {
case is SpecificTask:
// execute a SpecificTask
return "Some Result" as! T.Result // <=== This is very fragile
default:
throw SomeError.error
}
}
}

This is fragile because of the as! T.Result. If you change the result type of SpecificTask to something other than String, it'll crash. But the important point is the case is SpecificTask, which is determined at runtime (dynamic dispatch). If you need task, and I assume you do, you'd swap that with if let task = task as? SpecificTask.

Before going down that road, I'd reconsider the design and see how this will really be called. Since the Result type is generic, you can't call arbitrary Tasks in a loop (since all the return values have to match). So it makes me wonder what kind of code can actually call run.

swift subclasses used in generics don't get called when inheriting from NSObject

I was able to confirm your results and submitted it as a bug, https://bugs.swift.org/browse/SR-10617. Turns out this is a known issue! I was informed (by good old Hamish) that I was duplicating https://bugs.swift.org/browse/SR-10285.

In my bug submission, I created a clean compact reduction of your example, suitable for sending to Apple:

protocol P {
init()
func doThing()
}

class Wrapper<T:P> {
func go() {
T().doThing()
}
}

class A : NSObject, P {
required override init() {}
func doThing() {
print("A")
}
}

class B : A {
required override init() {}
override func doThing() {
print("B")
}
}

Wrapper<B>().go()

On Xcode 9.2, we get "B". On Xcode 10.2, we get "A". That alone is enough to warrant a bug report.

In my report I listed three ways to work around the issue, all of which confirm that this is a bug (because none of them should make any difference):

  • make the generic parameterized type's constraint be A instead of P

  • or, mark the protocol P as @objc

  • or, don't have A inherit from NSObject


UPDATE: And it turns out (from Apple's own release notes) there's yet another way:

  • mark A's init as @nonobjc

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

Swift generics with variables

You need to declare the readings params as [T].

class GeneralUpdate<T: Reading> {
var readingType: ReadingType
var dataSource: DataSource
var readings: [T]

init(readingType: ReadingType, dataSource: DataSource, readings: [T]) {
self.readingType = readingType
self.dataSource = dataSource
self.readings = readings
}
}

And please get rid of all that ugly ! Implicitly Unwrapped Optionals.

How to properly use generics in Swift to store different types of objects in a list in Realm?

This is not going to work

let ChildObjects = List<O>() //...two possible types: subjects & mistakes

Realm Collections (Result, List) are homogenous and can only store zero or more instances of one object type; e.g. you can't store different classes of Object in the same List object

Generics are great but when you're working with different (possibly unrelated) objects, subjects and mistakes, using a generic is probably the wrong approach because queries and the actually data will be quite different.

Keep your data separate

let subjects = List<SubjectClass>()

and

let mistakes = List<MistakeClass>()

of if there is some connection make it one class

class SubjectClass: Object {
@objc dynamic var subject_name = ""
@objc dynamic var is_mistake = false
}

of if a subject can have lots of mistakes

class SubjectClass: Object {
@objc dynamic var subject_name = ""
let allMyMistakes = List<MistakeClass<>
}

For some data modeling info, review this answer and then a fantasic answer from @DávidPásztor here

How do you store Swift object with generic properties in Core Data?

Using generics and Core Data together is not easy so I think that in this case where you have only two parameter type, Int and Double, that the easiest solution is to create an NSManagedObject subclass for each. It's not the most optimal solution but it is quite straightforward and easy to work with. You will of course need to duplicate your fetch request etc but on the other hand you won't need to do any complicated casting to the correct generic type.

Here is how the Double variant could look

class CDParameterProfileDouble: NSManagedObject {
@NSManaged var parameterName: String
@NSManaged var lowerBound: Double
@NSManaged var upperBound: Double
@NSManaged var units: String
@NSManaged var controlStep: Double

func toDomain() -> ParameterProfile<Double> {
ParameterProfile(parameterName: self.parameterName,
bounds: self.lowerBound...self.upperBound,
units: self.units,
controlStep: self.controlStep)
}
}

and the same for Int

class CDParameterProfileInt: NSManagedObject {
@NSManaged var parameterName: String
@NSManaged var lowerBound: Int
@NSManaged var upperBound: Int
@NSManaged var units: String
@NSManaged var controlStep: Int

func toDomain() -> ParameterProfile<Int> {
ParameterProfile(parameterName: self.parameterName,
bounds: self.lowerBound...self.upperBound,
units: self.units,
controlStep: self.controlStep)
}
}


Related Topics



Leave a reply



Submit