Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 835
Mike Sabatini Avatar asked Jul 08 '14 15:07

Mike Sabatini


3 Answers

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

like image 54
David Berry Avatar answered Nov 19 '22 23:11

David Berry


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:

  1. Called with integer: 1
  2. Doing something
  3. 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.

like image 29
fulvio Avatar answered Nov 19 '22 22:11

fulvio


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

like image 1
Connor Avatar answered Nov 19 '22 21:11

Connor