Swift compile time errors when using generics and accessing dictionary's values

Question

Below I have a class, B, with a generic type and the generic type has a subclass type constraint. In a separate class, A, I create a dictionary property, values, with the key as String and value as B. Class A then has methods to return or set values in the dictionary such that the values are not constrained to a single type (they maintain their generic SomeType type which is a subclass of NSObject). However, this produces the following two errors noted inline below:

class A: NSObject {

    var values = [String : B]()

    func get<SomeType: NSObject>(key: String) -> B<SomeType>? { 
        // #1 Error on line below: cannot convert return expression of type 'B<NSObject>?' to return type 'B<SomeType>?'
        return values[key]
    }

    func set<SomeType: NSObject>(key: String, value: B<SomeType>) {
        // #2 Error on line below: cannot assign value of type 'B<SomeType>' to type 'B<NSObject>?'
        values[key] = value
    }
}

class B<SomeType: NSObject>: NSObject {

}

I've attempted various forms of declaring the values dictionary to tell the compiler that SomeType is a subclass of NSObject and everything is going to be ok, but have been unsuccessful. Similarly to this question, I'm a bit stumped because the methods define SomeType as a subclass of NSObject and therefore things appear to be type safe when setting and getting from values.

I could remove the generic types from the methods and instead force the type to be <NSObject>, but then I'd run into the same problem as noted here.


Show source
| generics   | swift   | ios   | dictionary   2017-01-05 19:01 1 Answers

Answers ( 1 )

  1. 2017-01-05 19:01

    This may not be doing what you think it's doing:

    var values = [String : B]()
    

    This isn't really [String : B], it's [String : B<NSObject>]. (I'm actually kind of surprised that this is legal syntax; I'd be tempted to open a bugreport about that; B isn't a proper type in Swift.) You may realize this already, but it's the first important note.

    The second important note is that generic types are not covariant in Swift. A Thing<Cat> is not a subtype of Thing<Animal>. There are some type-theory reasons for this, and there are some technical implementation reasons for this, but the important fact is that it's not possible in Swift.

    If you need to hold a variety of B types, then you'll need to build a type eraser. In your case, the type eraser could possibly be B<NSObject> with something like this:

    class B<SomeType: NSObject>: NSObject {
        let value: SomeType
    
        init(value: SomeType) {
            self.value = value
        }
    
        func liftGeneric() -> B<NSObject> {
            return B<NSObject>(value: value)
        }
    }
    

    If you need to hold just one kind of B type, then make A generic as well.

◀ Go back