可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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?
回答1:
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>
.
回答2:
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.
回答3:
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
回答4:
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
).
回答5:
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):
...
}
回答6:
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"