Putting two generic Arrays into one Swift Dictionary with Generics - ios

I'm trying to put two different generic types into a collection. In this example there are two arrays, one containing Ints and the other Strings.
let intArray = Array<Int>()
let stringArray = Array<String>()
let dict = [1:intArray, "one": stringArray]
The error reads Type of expression is ambiguous without more context.
So I tried specifying the Dictionary's type
let dict: [Hashable: Any] = [1:intArray, "one": stringArray]
This leads to two errors.
Using 'Hashable' as a concrete type conforming to protocol 'Hashable' is not supported.
Protocol 'Hashable' can only be used as a generic constraint because it has Self or associated type requirements
Adding import Foundation and using NSDictionary as the type works fine.
let dict: NSDictionary = [1:intArray, "one": stringArray]
But this should be possible in pure Swift too without using Foundation. What is the type the Dictionary has to have?
edit: This apparently has more to do with the type of the keys. They have to be of the same type, not just conform to Hashable.
let dict: [Int:Any] = [1:intArray, 2: stringArray]
This works. But is it possible to make the type of the value more precise? [Int:Array<Any>] does not work.

Elaborating on the answer from #RobNapier, here is a similar approach that uses enum for both, keys and values of the dictionary:
enum Key: Equatable, Hashable {
case IntKey(Int)
case StringKey(String)
var hashValue: Int {
switch self {
case .IntKey(let value) : return 0.hashValue ^ value.hashValue
case .StringKey(let value) : return 1.hashValue ^ value.hashValue
}
}
init(_ value: Int) { self = .IntKey(value) }
init(_ value: String) { self = .StringKey(value) }
}
func == (lhs: Key, rhs: Key) -> Bool {
switch (lhs, rhs) {
case (.IntKey(let lhsValue), .IntKey(let rhsValue)) : return lhsValue == rhsValue
case (.StringKey(let lhsValue), .StringKey(let rhsValue)) : return lhsValue == rhsValue
default: return false
}
}
enum Value {
case IntValue(Int)
case StringValue(String)
init(_ value: Int) { self = .IntValue(value) }
init(_ value: String) { self = .StringValue(value) }
}
var dict = [Key: Value]()
dict[Key(1)] = Value("One")
dict[Key(2)] = Value(2)
dict[Key("Three")] = Value("Three")
dict[Key("Four")] = Value(4)

What is the type the Dictionary has to have?
You may try:
let dict: [NSObject: Any] = [1: intArray, "one": stringArray]
The statement let dict: [Hashable: Any] = ... does not compile, because the type of the key of a Dictionary must be a concrete type conforming to Hashable, e.g. Int, String, etc. Hashable is not a concrete type.
The above suggestion works, because 1. NSObject is a concrete type (where you can instantiate objects from), 2. NSObject is a Hashable, and 3. because instances of subclasses of NSObjects will work here as well, and 4. the compiler can initialise NSObject subclasses from string and number literals.
If you don't like NSObject as the type of the key, you may create your own class or struct.

Note that your first attempt let dict = [1:intArray, "one": stringArray] works if you include Foundation; yielding an NSDictionary (so no need to explicitly state this type).
The reason why we can have these kinds of, apparently, generic dictionaries when using Foundation is the implicit type conversion performed (behind the hood) by the compiler when bridging Swift native types to Foundation.
let intArray : [Int] = [10, 20, 30]
let stringArray : [String] = ["foo", "baz", "bar"]
let dict = [1:intArray, "onx": stringArray]
print(dict.dynamicType)
for e in dict {
print(e.dynamicType, e.key.dynamicType, e.value.dynamicType)
}
/* __NSDictionaryI
(AnyObject, AnyObject) __NSCFString _SwiftDeferredNSArray
(AnyObject, AnyObject) __NSCFNumber _SwiftDeferredNSArray */
The keys as well as values in dict above are wrapped in type AnyObject; which can hold only reference (class) type objects; the compiler implicitly performs conversion of value types Int/String to Foundation reference types __NSCFNumber/__NSCFString. This is still an NSDictionary though; e.g. AnyObject itself does not conform to Hashable, so it can't be used as a key in a native Swift dictionary.
If you wish to create Swift-native "generic-key" dictionary, I'd suggest you create a wrapper (say a structure) that conforms to Hashable and that wraps over the underlying (various) key type(s). See e.g. (the somewhat outdated) thread
How to create Dictionary that can hold anything in Key? or all the possible type it capable to hold

Related

iOS Swift4 how to reconcile T.Type and type(of:) to pass dynamic class type as function parameter?

I'm trying implement generic storage of configuration parameters by using class type string as a dictionary key. The idea is that the retrieve function will return an object of proper type. Each type is unique in the storage. However, when I call the function, I'm getting a Swift compiler error and am not sure how interpret it:
Compiler Error:
Cannot invoke 'retrieve' with an argument list of type '(type: Any.Type)
I checked the documentation, and it seems like the type(of:) method is supposed to return runtime class, while the compiler makes it look like it's complaining because it thinks I'm passing a type of Any
How do I pass Swift class name as a function parameter without using an instance of that class?
func retrieve<T>(type: T.Type) -> T? {
let valueKey = String(describing: type)
print("retrieving: \(valueKey)")
return dictionary[valueKey] as? T
}
func update(with value: Any) {
let valueKey = String(describing: type(of: value))
print("Updating: \(valueKey)")
dictionary[valueKey] = value
}
let testCases: [Any] = [1, 2.0, "text"]
for testCase in testCases {
subject.update(with: testCase)
//Compiler Error: Cannot invoke 'retrieve' with an argument list of type '(type: Any.Type)
let t = type(of: testCase)
let retrieved = subject.retrieve(type: t)
//check for equality
}
//this works
expect(subject.retrieve(type: Int.self)).to(equal(1))
expect(subject.retrieve(type: Double.self)).to(equal(2.0))
expect(subject.retrieve(type: String.self)).to(equal("text"))
I've done more testing, and see that it appears my array does not honor the type(of: ) documentation, and this function returns the same object as "Any":
func retrieve<T>(sample: T) -> T? {
let valueKey = String(describing: type(of: sample))
print("retrieving: \(valueKey)") //always retrieves same object "Any"
return dictionary[valueKey] as? T
}
Updated: Thank you for responses, to clarify - test cases were intended to begin with simple types, then progress to more complex classes. The actual implementation would store completely unique instances of custom types, not Strings or Ints.
let testCases: [Any] = [ConnectionConfig(...),
AccountID("testID"),
AccountName("testName")]
The tests recognize the generic nature of the retrieve function and assign appropriate types, as evidenced by code completion:
expect(subject.retrieve(type: ConnectionConfig.self)?.ip).to(equal("defaultIP"))
expect(subject.retrieve(type: AccountID.self)?.value).to(equal("testId"))
The intended end use within RxSwift context: provide the generic storage to a class and allow it to pull the appropriate values for configuration parameters. If no value exists, an error is thrown and is handled by a separate error handler:
class RxConfigConsumer: ConfigConsumer {
var connection: ConnectionConfig?
var accountID: AccountID?
init(with provider: ConfigProvider) {
connection = provider.retrieve(type: ConnectionConfig.self)
accountID = provider.retrieve(type: AccountID.self)
//etc
}
}
The combination of a generic with a metatype (.Type) is very weird and is probably what's tripping you up. If you get rid of the generic things work as you would expect:
func retrieve(_ T:Any.Type) {
print(type(of:T))
}
let testCases: [Any] = [1, 2.0, "text"]
for testCase in testCases {
retrieve(type(of:testCase))
}
// Int.Type, Double.Type, String.Type
If you really want the generic, then get rid of the .Type and write it like this:
func retrieve<T>(_ t:T) {
print(type(of:t))
}
let testCases: [Any] = [1, 2.0, "text"]
for testCase in testCases {
retrieve(type(of:testCase))
}
// Int.Type, Double.Type, String.Type
Even then, however, it's unclear to me what the point is of passing the metatype.
The short answer is what you're trying to do is impossible, because there is no way to type-annotate the following line of code:
let retrieved = subject.retrieve(type: t)
What is the static type, known at compile-time, of retrieved? It can't change at run-time. It certainly can't change from iteration to iteration. The compiler needs to allocate space for it. How much space does it require? Does it require space on the stack or heap? There's no way to know. The best we can say is that it's Any and put a box around it. 1 doesn't even have a proper type anyway. It's just an integer literal. It could be a Float or many other things (try let x: Float = 1 and see).
The answer is you can't build a loop like this. Your individual test cases are the right ones. Once you create an [Any], it is very difficult to get "real" types back out. Avoid it. If you have a more concrete problem beyond the example you've given, we can discuss how to deal with that, but I believe outside of a unit test, this specific problem shouldn't come up anyway.
This is an interesting question and you can run the following codes in a playground.
The first step is to solve the T.Type parameter. It's hard to put it into a function call. So to achieve your goal, we can use T but T.Type.
class MySubject {
var dictionary : [String : Any] = [:]
func retrieve<T>(type1: T) -> T? {
let valueKey = String(describing: (type(of: type1)))
print("retrieving: \(valueKey)")
return dictionary[valueKey] as? T
}
func update(with value: Any) {
let valueKey = String(describing: type(of: value))
print("Updating: \(valueKey)")
dictionary[valueKey] = value
}
}
var subject : MySubject = MySubject()
let testCases: [Any] = [1, 2.0, "text"]
for testCase in testCases {
subject.update(with: testCase)
//Compiler Error: Cannot invoke 'retrieve' with an argument list of type '(type: Any.Type)
let retrieved = subject.retrieve(type1: testCase)
//check for equality
}
The compilation is correct. But as you said, the return value is nil as a result of a generic retrieve Function. In order to achieve your goal, we may skip the generic way, use Any directly.
class MySubject {
var dictionary : [String : Any] = [:]
func retrieve(type1: Any) -> Any? {
let valueKey = String(describing: (type(of: type1)))
print("retrieving: \(valueKey)")
return dictionary[valueKey]
}
func update(with value: Any) {
let valueKey = String(describing: type(of: value))
print("Updating: \(valueKey)")
dictionary[valueKey] = value
}
}
var subject : MySubject = MySubject()
let testCases: [Any] = [1, 2.0, "text"]
for testCase in testCases {
subject.update(with: testCase)
//Compiler Error: Cannot invoke 'retrieve' with an argument list of type '(type: Any.Type)
let retrieved = subject.retrieve(type1: testCase)
//check for equality
}
Currently everything is perfect as you wish. But this brings up an interesting thought about generic. Is it O.K. or right to use generic here? As we know, there is a presumption in generic, which is represented by letters T, U, V. They have one common meaning: Type. When we try to use generic, we assume every parameter should have only one unique type. So in first case, Any is the only type and should be accepted without question in a generic call. There is no other type will be revealed during function call.
This kind of misunderstanding roots from the use of "let testCases: [Any] = [1, 2.0, "text"]. Although swift allows you to write this way, they are not a normal array. They are a list which contains different type essentially. So you can ignore fancy generic here without any regrets. Just pick the Any to solve your problem.

Dictionary of type [NSObject: AnyObject] has no member "value(forKeyPath: ...)"

I'm converting an app to swift3 and encountering the following problem.
#objc required init(response: HTTPURLResponse, representation: [NSObject : AnyObject])
{
if (representation.value(forKeyPath: "title") is String) {
self.title = **representation.value**(forKeyPath: "title") as! String
}
I get the following error:
Value of type [NSObject:AnyObject] has no member value.
In the old version of the code I was just using AnyObject as type for representation, but if I do so I get the error AnyObject is not a subtype of NSObject there:
if (representation.value(forKeyPath: "foo") is String) {
let elementObj = Element(response: response, representation:**representation.value(forKeyPath: "foo")**!)
}
You're mixing Objective-C and Swift styles. Better to actually decide.
Bridging back to NSDictionary is not automatic.
Consider:
let y: [NSObject: AnyObject] = ["foo" as NSString: 3 as AnyObject] // this is awkward, mixing Swift Dictionary with explicit types yet using an Obj-C type inside
let z: NSDictionary = ["foo": 3]
(y as NSDictionary).value(forKeyPath: "foo") // 3
// y["foo"] // error, y's keys are explicitly typed as NSObject; reverse bridging String -> NSObject/NSString is not automatic
y["foo" as NSString] // 3
y["foo" as NSString] is Int // true
z["foo"] // Bridging here is automatic though because NSDictionary is untyped leaving compiler freedom to adapt your values
z["foo"] is Int // true
// y.value // error, not defined
// Easiest of all:
let ynot = ["foo": 3]
ynot["foo"] // Introductory swift, no casting needed
ynot["foo"] is Int // Error, type is known at compile time
Reference:
https://developer.apple.com/library/content/documentation/Swift/Conceptual/BuildingCocoaApps/WorkingWithCocoaDataTypes.html
Note the explicit use of 'as' required to get a String back to NSString. Bridging is not hidden because they want you to use value types (String) over reference types (NSString). So this is deliberately more cumbersome.
One of the primary advantages of value types over reference types is
that they make it easier to reason about your code. For more
information about value types, see Classes and Structures in The Swift
Programming Language (Swift 3), and WWDC 2015 session 414 Building
Better Apps with Value Types in Swift.

Is Float, Double, Int an AnyObject?

I read inline documentation of Swift and I am bit confused.
1) Any is a protocol that all types implicitly conform.
2) AnyObject is a protocol to which all classes implicitly conform.
3) Int, Float, Double are structs
Here is a sample code:
import UIKit
func passAnyObject(param: AnyObject) {
print(param)
}
class MyClass {}
struct MyStruct {}
let a: Int = 1
let b = 2.0
let c = NSObject()
let d = MyClass()
let e = MyStruct()
passAnyObject(a)
passAnyObject(b)
passAnyObject(c)
passAnyObject(d)
//passAnyObject(e) // Argument type 'MyStruct' does not conform to expected type 'AnyObject'
if a is AnyObject { // b, d, e is also AnyObject
print("\(a.dynamicType) is AnyObject")
}
What I don't understand is why Int, Double, Float are AnyObjects? Why compiler doesn't say anything? Those types are declared as structs. Struct MyStruct cannot be passed to the method on the top because it does not conform to AnyObject.
Could you help me understand why Int, Double and Float are AnyObject or why compiler thinks they are?
Because you have Foundation imported, Int, Double, and Float get converted to NSNumber when passed to a function taking an AnyObject. Type String gets converted to NSString. This is done to make life easier when calling Cocoa and Cocoa Touch based interfaces. If you remove import UIKit (or import Cocoa for OS X), you will see:
error: argument type 'Int' does not conform to expected type 'AnyObject'
when you call
passAnyObject(a)
This implicit conversion of value types to objects is described here.
Update for Swift 3 (Xcode 8 beta 6):
Passing an Int, Double, String, or Bool to a parameter of type AnyObject now results in an error such as Argument of type 'Int' does not conform to expected type 'AnyObject'.
With Swift 3, implicit type conversion has been removed. It is now necessary to cast Int, Double, String and Bool with as AnyObject in order to pass it to a parameter of type AnyObject:
let a = 1
passAnyObject(a as AnyObject)
Good find! UIKit actually converts them to NSNumber - also mentioned by #vacawama. The reason for this is, sometimes you're working with code that returns or uses AnyObject, this object could then be cast (as!) as an Int or other "structs".
class Test {
static func test() {
let anyObjectsValues: [AnyObject] = [1, "Two", 3, "Four"] as [AnyObject]
anyObjectsValues.forEach { (value) in
switch value {
case is Int:
print("\(value) is an Int!")
case is String:
print("\(value) is a String!")
default:
print("\(value) is some other type!")
}
}
}
}
I have not imported UIKit or Foundation frameworks. Why compiler is not giving any error? Even it printing the result.
Output:
1 is an Int!
Two is a String!
3 is an Int!
Four is a String!
Does anybody have an idea?

How to put different values and keys into a dictionary [duplicate]

I want to create a Dictionary that does not limit the key type (like NSDictionary)
So I tried
var dict = Dictionary<Any, Int>()
and
var dict = Dictionary<AnyObject, Int>()
resulting
error: type 'Any' does not conform to protocol 'Hashable'
var dict = Dictionary<Any, Int>()
^
<REPL>:5:12: error: cannot convert the expression's type '<<error type>>' to type '$T1'
var dict = Dictionary<Any, Int>()
^~~~~~~~~~~~~~~~~~~~~~
OK, I will use Hashable
var dict = Dictionary<Hashable, Int>()
but
error: type 'Hashable' does not conform to protocol 'Equatable'
var dict = Dictionary<Hashable, Int>()
^
Swift.Equatable:2:8: note: '==' requirement refers to 'Self' type
func ==(lhs: Self, rhs: Self) -> Bool
^
Swift.Hashable:1:10: note: type 'Hashable' does not conform to inherited protocol 'Equatable.Protocol'
protocol Hashable : Equatable
^
<REPL>:5:12: error: cannot convert the expression's type '<<error type>>' to type '$T1'
var dict = Dictionary<Hashable, Int>()
^~~~~~~~~~~~~~~~~~~~~~~~~~~
So Hashable inherited from Equatable but it does not conform to Equatable??? I don't understand...
Anyway, keep trying
typealias KeyType = protocol<Hashable, Equatable> // KeyType is both Hashable and Equatable
var dict = Dictionary<KeyType, Int>() // now you happy?
with no luck
error: type 'KeyType' does not conform to protocol 'Equatable'
var dict = Dictionary<KeyType, Int>()
^
Swift.Equatable:2:8: note: '==' requirement refers to 'Self' type
func ==(lhs: Self, rhs: Self) -> Bool
^
Swift.Hashable:1:10: note: type 'KeyType' does not conform to inherited protocol 'Equatable.Protocol'
protocol Hashable : Equatable
^
<REPL>:6:12: error: cannot convert the expression's type '<<error type>>' to type '$T1'
var dict = Dictionary<KeyType, Int>()
^~~~~~~~~~~~~~~~~~~~~~~~~~
I am so lost now, how can I make compiler happy with my code?
I want to use the dictionary like
var dict = Dictionary<Any, Int>()
dict[1] = 2
dict["key"] = 3
dict[SomeEnum.SomeValue] = 4
I know I can use Dictionary<NSObject, Int>, but it is not really what I want.
Swift 3 update
You can now use AnyHashable which is a type-erased hashable value, created exactly for scenarios like this:
var dict = Dictionary<AnyHashable, Int>()
I believe that, as of Swift 1.2, you can use an ObjectIdentifier struct for this. It implements Hashable (and hence Equatable) as well as Comparable. You can use it to wrap any class instance. I'm guessing the implementation uses the wrapped object's underlying address for the hashValue, as well as within the == operator.
I took the liberty of cross-posting / linking to this question on a separate post on the Apple Dev forums and this question is answered here.
Edit
This answer from the above link works in 6.1 and greater:
struct AnyKey: Hashable {
private let underlying: Any
private let hashValueFunc: () -> Int
private let equalityFunc: (Any) -> Bool
init<T: Hashable>(_ key: T) {
underlying = key
// Capture the key's hashability and equatability using closures.
// The Key shares the hash of the underlying value.
hashValueFunc = { key.hashValue }
// The Key is equal to a Key of the same underlying type,
// whose underlying value is "==" to ours.
equalityFunc = {
if let other = $0 as? T {
return key == other
}
return false
}
}
var hashValue: Int { return hashValueFunc() }
}
func ==(x: AnyKey, y: AnyKey) -> Bool {
return x.equalityFunc(y.underlying)
}
Dictionary is struct Dictionary<Key : Hashable, Value>...
Which means that Value could be anything you want, and Key could be any type you want, but Key must conform to Hashable protocol.
You can't create Dictionary<Any, Int>() or Dictionary<AnyObject, Int>(), because Any and AnyObject can't guarantee that such a Key conforms Hashable
You can't create Dictionary<Hashable, Int>(), because Hashable is not a type it is just protocol which is describing needed type.
So Hashable inherited from Equatable but it does not conform to
Equatable??? I don't understand...
But you are wrong in terminology. Original error is
type 'Hashable' does not conform to inherited protocol 'Equatable.Protocol'
That means that Xcode assuming 'Hashable' as some type, but there is no such type. And Xcode treat it as some kind empty type, which obviously does not conform any protocol at all (in this case it does not conform to inherited protocol Equatable)
Something similar happens with KeyType.
A type alias declaration introduces a named alias of an existing type into your program.
You see existing type. protocol<Hashable, Equatable> is not a type it is protocol so Xcode again tells you that type 'KeyType' does not conform to protocol 'Equatable'
You can use Dictionary<NSObject, Int> just, because NSObject conforms Hashable protocol.
Swift is strong typing language and you can't do some things like creating Dictionary that can hold anything in Key. Actually dictionary already supports any can hold anything in Key, which conforms Hashable. But since you should specify particular class you can't do this for native Swift classes, because there is no such master class in Swift like in Objective-C, which conforms air could conform (with a help of extensions) to Hashable
Of course you can use some wrapper like chrisco suggested. But I really can't imagine why you need it. It is great that you have strong typing in Swift so you don't need to worry about types casting as you did in Objective-C
Hashable is just a protocol so you can't specify it directly as a type for the Key value. What you really need is a way of expressing "any type T, such that T implements Hashable. This is handled by type constraints in Swift:
func makeDict<T: Hashable>(arr: T[]) {
let x = Dictionary<T, Int>()
}
This code compiles.
AFAIK, you can only use type constraints on generic functions and classes.
This doesn't exactly answer the question, but has helped me.
The general answer would be implement Hashable for all your types, however that can be hard for Protocols because Hashable extends Equatable and Equatable uses Self which imposes severe limitations on what a protocol can be used for.
Instead implement Printable and then do:
var dict = [String: Int]
dict[key.description] = 3
The implementation of description has to be something like:
var description : String {
return "<TypeName>[\(<Field1>), \(<Field2>), ...]"
}
Not a perfect answer, but the best I have so far :(
This does not answer the OP's question, but is somewhat related, and may hopefully be of use for some situations. Suppose that what you really want to do is this:
public var classTypeToClassNumber = [Any.Type : Int]()
But Swift is telling you "Type 'Any.Type' does not conform to protocol Hashable".
Most of the above answers are about using object instances as a dictionary key, not using the type of the object. (Which is fair enough, that's what the OP was asking about.) It was the answer by Howard Lovatt that led me to a usable solution.
public class ClassNumberVsClassType {
public var classTypeToClassNumber = [String : Int]()
public init() {
classTypeToClassNumber[String(describing: ClassWithStringKey.self)] = 367622
classTypeToClassNumber[String(describing: ClassBasedOnKeyedItemList3.self)] = 367629
classTypeToClassNumber[String(describing: ClassBasedOnKeyedItemList2.self)] = 367626
classTypeToClassNumber[String(describing: ClassWithGuidKey.self)] = 367623
classTypeToClassNumber[String(describing: SimpleStruct.self)] = 367619
classTypeToClassNumber[String(describing: TestData.self)] = 367627
classTypeToClassNumber[String(describing: ETestEnum.self)] = 367617
classTypeToClassNumber[String(describing: ClassBasedOnKeyedItemList0.self)] = 367624
classTypeToClassNumber[String(describing: ClassBasedOnKeyedItemList1.self)] = 367625
classTypeToClassNumber[String(describing: SimpleClass.self)] = 367620
classTypeToClassNumber[String(describing: DerivedClass.self)] = 367621
}
public func findClassNumber(_ theType : Any.Type) -> Int {
var s = String(describing: theType)
if s.hasSuffix(".Type") {
s = s.substring(to: s.index(s.endIndex, offsetBy: -5)) // Remove ".Type"
}
let classNumber = _classTypeToClassNumber[s]
return classNumber != nil ? classNumber! : -1
}
}
EDIT:
If the classes involved are defined in different modules, and may have conflicting class names if you neglect the module name, then substitute "String(reflecting:" for "String(describing:", both when building up the dictionary and when doing the lookup.
You can use the class name as a Hashable, e.g.:
var dict = [String: Int]
dict[object_getClassName("key")] = 3
See How do I print the type or class of a variable in Swift? for how you might get the class name.

Swift closure as values in Dictionary

I'm trying to use an Objective-C library which expects a NSDictionary as its return type. Within the NSDictionary, I can return values of any type, including blocks.
I cannot figure out if there is a way to write an analogous swift method that returns a Dictionary with a closure or a string as a possible value type.
I can't use AnyObject as the value type for the dictionary so this doesn't work:
Dictionary<String,AnyObject> = ["Key":{(value:AnyObject) -> String in return value.description]
I get a Does not conform to protocol error from the compiler regarding the closure and AnyObject.
Is there a higher level type or protocol that both closures and basic types adhere to that I can use as the value type in a Dictionary?
Your basic problem is that in Objective-C closures (aka blocks) are represented as NSObject (or more precisely are transparently converted to NSObjects) while in Swift there is no such mapping. This means that closures can not be directly stored in a Dictionary (short of using objective-c glue)
The closest I can come up with is something along the lines of wrapping the value in an enum:
enum DataType {
case AsString(String)
case AsClosure((AnyObject)->String)
}
var dict:Dictionary<String,DataType> = [
"string":DataType.AsString("value"),
"closure":DataType.AsClosure({(argument:AnyObject) -> String in
return "value"
}
)
]
Which is probably a better solution anyway, because this way you have an explicit typing associated with individual arguments instead of it being implicit using some sort of inflection.
Alternatively, you could only wrap the closure and use a dictionary of type Dictionary<String,Any>.
If you still need a workaround, here is one; usage looks like this:
var d : [String : AnyObject] = [:]
d["a"] = Blocks.voidBlockToId({ println("Doing something") })
d["b"] = "Some string"
d["c"] = Blocks.intBlockToId({ i in println("Called with integer: \(i)") })
Blocks.toIntBlock(d["c"])(1)
Blocks.toVoidBlock(d["a"])()
println(d["b"])
Output is:
Called with integer: 1
Doing something
Some string
The Blocks class is defined like this in Objective-C (with corresponding header and bridging header, I won't put those here):
typedef void(^VoidBlock)(void);
typedef void(^IntBlock)(int);
#implementation Blocks
+ (id) voidBlockToId: (VoidBlock) block { return block; }
+ (VoidBlock) toVoidBlock: (id) block { return (VoidBlock)block; }
+ (id) intBlockToId: (IntBlock) block { return block; }
+ (IntBlock) toIntBlock:(id)block { return (IntBlock)block; }
#end
You also need to add a new xyzBlockToId and toXyzBlock method for every new closure-type you want to use. It's pretty ugly, but it works.
There is another type, Any, that object, structs and primitives all conform to but functions do not. There is no general function type, but you can describe a function type as its arguments and return value like this:
Dictionary<String, (AnyObject) -> String>
Function Types
Could you use an NSMutableDictionary?
Alternatively, this seemed to work for me using your example:
1> import Foundation
2> var myDict: [String: (NSObject) -> String] = ["Key":{(value:NSObject) -> String in return value.description}]
myDict: [String : (NSObject) -> String] = {
[0] = {
key = "Key"
value =
}
}
3> myDict["Key"]!("Testing")
$R2: String = "Testing"
Hmm, maybe this Swift-Code doesn't really help, because you want to have heterogenous dictionaries.
It's also not possible to put closures into an NSDictionary, it seems (as a closure does not conform to AnyObject).
You could also roll your own higher type using an enum. You need the dictionary values to be either strings or functions which return strings, so define a type to represent that:
enum MyDictVal {
case ValString(String)
case ValFunc(AnyObject -> String)
}
Then, you can put it in a dictionary:
let d: Dictionary<String, MyDictVal> = [
"a": .ValString("a")
, "b": .ValFunc({ (value) in value.description })
]
Then you'll need to process the dictionary values using pattern matching:
switch d["b"] {
case .ValString(let s):
...
case .ValFunc(let f):
...
}
A more "generic" solution which should work with Any object, but shown with closures and function references. Drop it into a playground and try it out!
// Wrapper for sticking non-objects in NSDictionary instances
class ObjectWrapper {
let value: T
init(_ value: T) {
self.value = value
}
}
// convenience to downcast `as! ObjectWrapper` and return its value
func getValueFromObjectWrapper(a: AnyObject) -> T {
return (a as! ObjectWrapper).value
}
func wrappedObjectsInDictionary() -> NSDictionary {
var dict = NSMutableDictionary()
let appendToFoo: (String) -> String = NSString.stringByAppendingString("foo")
let firstChar: (String) -> Character = { $0[$0.startIndex] }
dict.setObject(ObjectWrapper(firstChar), forKey: "stringToChar")
dict.setObject(ObjectWrapper(appendToFoo), forKey: "stringTransformer")
return dict.copy() as! NSDictionary
}
let dict = wrappedObjectsInDictionary()
let appendToFoo: (String) -> String = getValueFromObjectWrapper(dict["stringTransformer"]!)
let strToChar: (String) -> Character = getValueFromObjectWrapper(dict["stringToChar"]!)
appendToFoo("bar") // "foobar"
strToChar("bar") // "b"

Resources