How to Store a Value of Type Class≪Classimplementingprotocol≫ in a Dictionary of Type [String:Class≪Protocol≫] in Swift

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

Turn dictionary into custom type

Based on the comment by tbogosia I looked at what Swift 4 offers and it looks like the new Codable is the way to go.

Apple doc right here:

https://developer.apple.com/documentation/foundation/archives_and_serialization/encoding_and_decoding_custom_types

It would seem that for lower version of Swift there is no other option but to use custom init functions for every custom 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
}
}

Make a Swift dictionary where the key is Type?

Unfortunately, it's currently not possible for metatype types to conform to protocols (see this related question on the matter) – so CellThing.Type does not, and cannot, currently conform to Hashable. This therefore means that it cannot be used directly as the Key of a Dictionary.

However, you can create a wrapper for a metatype, using ObjectIdentifier in order to provide the Hashable implementation. For example:

/// Hashable wrapper for a metatype value.
struct HashableType<T> : Hashable {

static func == (lhs: HashableType, rhs: HashableType) -> Bool {
return lhs.base == rhs.base
}

let base: T.Type

init(_ base: T.Type) {
self.base = base
}

func hash(into hasher: inout Hasher) {
hasher.combine(ObjectIdentifier(base))
}
// Pre Swift 4.2:
// var hashValue: Int { return ObjectIdentifier(base).hashValue }
}

You can then also provide a convenience subscript on Dictionary that takes a metatype and wraps it in a HashableType for you:

extension Dictionary {
subscript<T>(key: T.Type) -> Value? where Key == HashableType<T> {
get { return self[HashableType(key)] }
set { self[HashableType(key)] = newValue }
}
}

which could then use like so:

class CellThing {}
class A : CellThing {}
class B : CellThing {}

var recycle: [HashableType<CellThing>: [CellThing]] = [:]

recycle[A.self] = [A(), A(), A()]
recycle[B.self] = [B(), B()]

print(recycle[A.self]!) // [A, A, A]
print(recycle[B.self]!) // [B, B]

This should also work fine for generics, you would simply subscript your dictionary with T.self instead.


Unfortunately one disadvantage of using a subscript with a get and set here is that you'll incur a performance hit when working with dictionary values that are copy-on-write types such as Array (such as in your example). I talk about this issue more in this Q&A.

A simple operation like:

recycle[A.self]?.append(A())

will trigger an O(N) copy of the array stored within the dictionary.

This is a problem that is aimed to be solved with generalised accessors, which have been implemented as an unofficial language feature in Swift 5. If you are comfortable using an unofficial language feature that could break in a future version (not really recommended for production code), then you could implement the subscript as:

extension Dictionary {
subscript<T>(key: T.Type) -> Value? where Key == HashableType<T> {
get { return self[HashableType(key)] }
_modify {
yield &self[HashableType(key)]
}
}
}

which solves the performance problem, allowing an array value to be mutated in-place within the dictionary.

Otherwise, a simple alternative is to not define a custom subscript, and instead just add a convenience computed property on your type to let you use it as a key:

class CellThing {
// Convenience static computed property to get the wrapped metatype value.
static var hashable: HashableType<CellThing> { return HashableType(self) }
}

class A : CellThing {}
class B : CellThing {}

var recycle: [HashableType<CellThing>: [CellThing]] = [:]

recycle[A.hashable] = [A(), A(), A()]
recycle[B.hashable] = [B(), B()]

print(recycle[A.hashable]!) // [A, A, A]
print(recycle[B.hashable]!) // [B, B]

Create a dictionary of generic variables in swift

variable1 and variable2 are not of type Entity but are in fact Entity<Float> and Entity<String> which are two unrelated types. Use a protocol to unite them:

protocol EntityProtocol { }

class Entity<T> : EntityProtocol {
var _value: T
var value: T { get { return _value } set {_value = newValue}}

init (defaultValue: T) {
_value = defaultValue
}
}

class FloatEntity: Entity<Float> {
}

class StringEntity: Entity<String> {
}

func run () {
let variable1: Entity = FloatEntity (defaultValue: 1)
let variable2: Entity = StringEntity (defaultValue: "")
var dictionary: Dictionary<String, EntityProtocol> = [
"One": FloatEntity (defaultValue: 1),
"Two": StringEntity (defaultValue: ""),
]
print (variable1)
print (variable2)
print (dictionary)
}

Determining Swift Types That Can Be Stored in UserDefaults

The Property List type set of UserDefaults is very limited. The supported types are

  • NSString → Swift String
  • NSNumber → Swift Int, Double or Bool
  • NSDate → Swift Date
  • NSData → Swift Data
  • Arrays and dictionaries containing the 4 value types.

Any is not supported unless it represents one of the 4 value or 2 collection types.

Property List compliant collection types can be written to UserDefaults with PropertyListSerialization (even in Swift).


There are two protocols to serialize custom types to Data

  • Codable can serialize structs and classes.
  • NSCoding can serialize subclasses of NSObject.

All types in the structs/classes must be encodable and decodable (means conform to the protocol themselves).


The APIs of PropertyListSerialization / PropertyListEncoder/-Decoder and NSKeyed(Un)Archiver provide robust error handling to avoid crashes.



Related Topics



Leave a reply



Submit