Swift Dictionary With Expansive Type Constraints - swift

I would like to define a dictionary that allows multiple specific types for values. I can currently define a dictionary like this:
var foo = [String: String]
which constrains the value to one specific type
OR
var foo = [String: AnyObject]
which constrains the value to AnyObject, which is not particularly constrained.
I'd like to do something like:
var foo = [String: <String, Int>]
which would allow me to insert only strings or ints as values in this dictionary.
I've considered the use of an enum as seen here: Generic dictionary value type in Swift but I would rather find a way that allows me to dynamically specify the types without expanding or creating a new enum.
var bar = [String: <String, Int, Bool, MyClass>]
It seems that I should be able to create a wrapper class with an internal dictionary of [String: AnyObject] that will allow for compile time type checking against an arbitrarily long list of classes that can be inserted.
class ECDictionary<T> {
private var internal_storage = [String: AnyObject]()
subscript(s: String) -> T {
get {
return internal_storage[s]
}
set(newValue) {
internal_storage[s] = newValue
}
}
}
var myECDictionary = ECDictionary<String, Int, OtherClass>()
I could build this with runtime type checking, but I'd rather it be enforced at compile time. Is this possible in any way?

If you want to a Dictionary where the value can be a String, an Int, a Bool or a MyClass you should
Define your protocol
protocol MyDictionaryValue { }
Conform String, Int, Bool and MyClass to MyDictionaryValue
extension String: MyDictionaryValue { }
extension Int: MyDictionaryValue { }
extension Bool: MyDictionaryValue { }
extension MyClass: MyDictionaryValue { }
Declare your dictionary
var dict = [String:MyDictionaryValue]()
Usage
dict["a"] = "a string"
dict["b"] = 1
dict["c"] = true
dict["d"] = MyClass()
dict["e"] = CGPointZero // error
dict["f"] = UIView() // error

Related

Swift: how to prevent duplicating code in two struct initializers conforming to the same protocol?

I have a protocol that defines a simple dictionary:
protocol P {
var dictionary: [String: Any] { get }
}
And two structs conforming to it:
struct S1: P {
var moreInfo: [String]
var dictionary: [String: Any]
}
struct S2: P {
var dictionary: [String: Any]
}
I want to initialize these structs from a given object, so the initializer would be something like this:
init(from data: [[String: Any]]) {
dictionary = process(data)
// Specifically in S1
moreInfo = moreInfoProcess(data)
}
What is the best solution to prevent duplicating the same code in S1 and S2 to process data and setting dictionary ?
Thank you for your help
The easiest way is to implement the common code in an extension to P (and make it static to avoid compilation errors)
extension P {
static func process(_ data: [[String: Any]]) -> [String: Any] {
var result = [String: Any]()
// processing code...
return result
}
}

Swift Loop through [String: Any] and get difference to Struct or Class properties

I have a struct with several properties. How can I write a function that takes a dictionary of type [String: Any] and creates another dictionary of type [String: Any] that contains only keys and values where the input dictionary's value was different from the struct property value with the same name?
A struct:
struct MyStruct {
var a: Bool = false
var b: String = "random"
var c: Int = 2
}
Desired function call:
let myStruct = MyStruct()
let input: [String: Any] = ["b": "test", "c": 2]
let result: [String: Any] = myStruct.getDiff(input)
Desired result for the example input:
result = ["b": "test"]
Besides a struct, how would this be done for comparing the [String: Any] to a class?
The specific syntax you've provided is probably not possible. Packing things into a [String: Any] likely loses too much information to be recovered. But your overall goal is certainly possible.
The important piece is input. Rather than [String: Any], we're going to use an explicit type, ValueChange:
let input = [
ValueChange(key: "b", changedTo: "test"),
ValueChange(key: "c", changedTo: 2),
]
Creating a new type like this allows us to capture all the types, and enforce certain rules, particularly that the values are Equatable:
struct ValueChange {
init<Value: Equatable>(key: String, changedTo newValue: Value) {...}
}
I'll come back to ValueChange in a moment, but first I want to go back to how it'll be used. Since you want a .getDiff(...) syntax, it'll be best to create an extension using a protocol:
protocol DictionaryDiffComputing {}
extension DictionaryDiffComputing {
func getDiff(_ changes: [ValueChange]) -> [String: Any] {
Dictionary(uniqueKeysWithValues:
changes.compactMap { $0.changedValue(self) })
}
}
The protocol has no requirements. It just exists to say "these types have the getDiff method." This method needs ValueChange to provide us a (String, Any) tuple if the value has changed.
This is where the problem gets interesting, I'll just show the answer and then discuss it.
struct ValueChange {
let changedValue: (_ object: Any) -> (String, Any)? // (key, newValue)
init<Value: Equatable>(key: String, changedTo newValue: Value) {
self.changedValue = { object in
// Get the old value as an Any using Mirror
guard let oldAnyValue: Any = Mirror(reflecting: object)
.children
.first(where: { $0.label == key })?
.value
else {
assertionFailure("Unknown key: \(key)")
return nil
}
// Make sure it's the correct type
guard let oldValue = oldAnyValue as? Value else {
assertionFailure("Bad type for values (\(oldAnyValue)). Expected: \(Value.self)")
return nil
}
// Compare the values
return newValue != oldValue ? (key, newValue) : nil
}
}
}
This uses Mirror to pull out the old value to compare as an Any type, then it converts it to the correct Value type. This is the power of the generic init. Since we know the type a compile time, we can capture it inside this closure, erasing that type from the outside world, but being able to work with it at runtime.
extension MyStruct: DictionaryDiffComputing {}
let myStruct = MyStruct()
myStruct.getDiff(input) // ["b": "test"]
What I really don't like about this answer is that it's very unsafe. Note the two calls to assertionFailure. There is nothing about ValueChange that ensures that the key exists or that the value is the correct type. If you change the name or type a property, your program will either crash or behave incorrectly, and there's nothing the compiler can do to help you.
You can make this a lot more type-safe and the code much simpler at the cost of a slightly more verbose calling syntax:
protocol DictionaryDiffComputing {}
struct ValueChange<Root> {
let changedValue: (_ object: Root) -> (String, Any)? // (key, newValue)
init<Value: Equatable>(key: String, keyPath: KeyPath<Root, Value>, changedTo newValue: Value) {
self.changedValue = { newValue != $0[keyPath: keyPath] ? (key, newValue) : nil }
}
}
extension DictionaryDiffComputing {
func getDiff(_ changes: [ValueChange<Self>]) -> [String: Any] {
Dictionary(uniqueKeysWithValues:
changes.compactMap { $0.changedValue(self) })
}
}
let myStruct = MyStruct()
let input: [ValueChange<MyStruct>] = [
ValueChange(key: "b", keyPath: \.b, changedTo: "test"),
ValueChange(key: "c", keyPath: \.c, changedTo: 2),
]
myStruct.getDiff(input)
If you use this approach, you know that the property exists on this type, and that the value is the correct type for that property. You also get some extra power, since you can use any key path you like starting at this root type. That means you can do things like:
ValueChange(key: "b_length", keyPath: \.b.count, changedTo: 4),
You could cleanup the requirement for key in ValueChange by adding some mapping dictionary of key path to key name (a static var protocol requirement for example), but I don't know of a way to generate this automatically, and I don't know any good way to convert a key path into an appropriate string.

Accessing swift dictionary members using AnyObject

I'm having trouble with understanding how to properly do unwrapping in Swift. Here is the situation:
I have these two classes:
class Alpha
{
var val: Int
init(x: Int)
{
val = x
}
func doSomething() -> Int
{
// does stuff and returns an Int value
return val
}
}
and a second class which is a subclass of the first
class Beta: Alpha
{
var str: [String] = []
override init(x: Int)
{
super.init(x)
}
func doSomething() -> String
{
// does stuff and returns a String
return str
}
}
So far so good, these two classes work as expected. But now I want to create a dictionary to store several of both these class objects. So I've created a class to manage the dictionary and add items to it.
class Gamma
{
var myDict: [String:AnyObject] = [:]
func addAlphaToDict(key: String, val: Int)
{
self.myDict[key] = Alpha(x: val).self
}
func addBetaToDict(key: String, val: Int)
{
self.myDict[key] = Beta(x: val).self
}
}
This also is working, but I think this could somehow be improved. The issue I'm having is now when I try to access values in the dictionary:
var foo = Gamma()
foo.addAlphaToDict(key: "blah", val: 45)
foo.addBetaToDict(key: "bluh", val: 23)
var result1 = (foo.myDict["blah"] as! Alpha).doSomething()
var result2 = (foo.myDict["bluh"] as! Beta).doSomething()
I find that the syntax here is very clunky and I feel like I'm doing it wrong, even though it works. I'm an experienced developer, but have only just started using Swift so I'm not really sure about certain things. Could someone with more Swift experience show me how to improve this code, or point me in the direction I should be going? Thanks!
You can use Alpha instead of AnyObject as your dictionary value in this case. Just downcast it to Beta when needed. Using AnyObject or Any as the dictionary key should be avoided as much as possible.
However, i have to say that this approach is bad. You need to have a clearer logic to decide when the key will be Beta other than just relaying on knowing which key you are passing to the dictionary.
In swift when you want a heterogeneous type with a finite number of possibilities, prefer an enum with associated values to Any or AnyObject. You can then use a switch to recover the type in an exhaustive and type safe way.
import UIKit
import PlaygroundSupport
enum DictionaryValues {
case integer(Int), string(String)
}
let dictionary: [String: DictionaryValues] = ["a": .integer(1), "b": .string("xyz")]
for (key, value) in dictionary {
switch value {
case .integer(let number):
print("Key \(key) is a number: \(number)")
case .string(let text):
print("Key \(key) is text: \(text)")
}
}

Class with unsafe mutable pointer parameter of generic type decided by the initializer

I am trying to create a class that operates differently based on its associated type. Apon initialization I will pass in a type to the initializer along with some other parameters not shown. My dilemma is the following bit of code I want to write but can't due to a compiler error.
class Foo
{
var data: UnsafeMutablePointer<T>
var type: T.Type
init?<T>(type: T.Type)
{
data = UnsafeMutablePointer<T>
self.type = type
}
func prepareForData()
{
data = UnsafeMutableRawPointer(memoryAddress + variableOffset).bindMemory(to:type.self, capacity:1)
}
}
where the class would be theoretically used like
let thing = Foo(Int) or let thing2 = Foo(coolStruct)
Is this even possible in Swift?
Can not check myself but the following should compile and work properly:
class Foo<T>
{
var data: UnsafeMutablePointer<T>
var type: T.Type
init?(type: T.Type)
{
data = UnsafeMutablePointer<T>()
self.type = type
}
func prepareForData()
{
data = UnsafeMutableRawPointer(memoryAddress + variableOffset).bindMemory(to:type.self, capacity:1)
}
}

Swift: Cast array of objects to array of sub type

Say I have an array of Animals and I'd like to cast it to an array of Cats. Here, Animal is a protocol that Cat adopts. I'd like something like let cats: [Cat] = animals as! [Cat] but this seg faults in compilation (btw I'm on both Linux Swift 3 and Mac Swift 2.2). My workaround is to just create a function that downcasts each item individually and adds it to a new array (see small example below). It produces the desired result, but isn't as clean as I'd like.
My questions are:
is this totally dumb and I'm just missing an easier way to do this?
how can I pass a type as the target parameter in the function below, rather than passing an instance? (e.g. I'd like to pass Cat.self rather than Cat(id:0) but doing so causes an error saying cannot convert Cat.Type to expected argument type Cat)
Here's what I have so far:
protocol Animal: CustomStringConvertible
{
var species: String {get set}
var id: Int {get set}
}
extension Animal
{
var description: String
{
return "\(self.species):\(self.id)"
}
}
class Cat: Animal
{
var species = "felis catus"
var id: Int
init(id: Int)
{
self.id = id
}
}
func convertArray<T, U>(_ array: [T], _ target: U) -> [U]
{
var newArray = [U]()
for element in array
{
guard let newElement = element as? U else
{
print("downcast failed!")
return []
}
newArray.append(newElement)
}
return newArray
}
let animals: [Animal] = [Cat(id:1),Cat(id:2),Cat(id:3)]
print(animals)
print(animals.dynamicType)
// ERROR: cannot convert value of type '[Animal]' to specified type '[Cat]'
// let cats: [Cat] = animals
// ERROR: seg fault
// let cats: [Cat] = animals as! [Cat]
let cats: [Cat] = convertArray(animals, Cat(id:0))
print(cats)
print(cats.dynamicType)
Am I missing an easier way to do this?
You can use map to make the conversion:
let cats: [Cat] = animals.map { $0 as! Cat }
how can I pass a type as the target parameter in the function below, rather than passing an instance?
First, you need to remove the instance parameter:
func convertArray<T, U>(array: [T]) -> [U] {
var newArray = [U]()
for element in array {
guard let newElement = element as? U else {
print("downcast failed!")
return []
}
newArray.append(newElement)
}
return newArray
}
Since you cannot specify type parameters explicitly, you need to provide the compiler with some info to deduce the type of U. In this case, all you need to do is to say that you are assigning the result to an array of Cat:
let cats: [Cat] = convertArray(animals)
As of Swift 4.1 using compactMap would be the preferred way, assuming you don't want the method to completely fail (and actually crash) when you have any other Animal (for example a Dog) in your array.
let animals: [Animal] = [Cat(id:1),Dog(id:2),Cat(id:3)]
let cats: [Cat] = animals.compactMap { $0 as? Cat }
Because compactMap will purge any nil values, you will end up with an array like so:
[Cat(1), Cat(3)]
As a bonus, you will also get some performance improvement as compared to using a for loop with append (since the memory space is not preallocated; with map it automatically is).