I have a dictionary that contains function calls plus a generic method for adding functions to that dictionary. The moment I downcast that generic function so that I can add it to the dictionary, the compiler crashes.
You can see that when you put the following code in a playground:
import Cocoa
class Email:NSObject {
var Subject:String = ""
}
class SMS:NSObject {
var Message:String = ""
}
var allfunc = Dictionary<String, (item: NSObject) -> Void>()
func test<T:NSObject>(type:T, myfunc:(item: T) -> Void) {
allfunc[NSStringFromClass(type.dynamicType)] = myfunc as? (item:NSObject) -> Void
}
test(Email(), {item in NSLog("\(item.Subject)")})
test(SMS(), {item in NSLog("\(item.Message)")})
for (key: String, value: (item: NSObject) -> Void) in allfunc {
if key == NSStringFromClass(Email().dynamicType) {
var mail = Email()
mail.Subject = "testing mail"
value(item: mail)
} else {
var sms = SMS()
sms.Message = "testing SMS"
value(item: sms)
}
}
When I change the test function to this then I't won't crash, but I will lose autocomplete:
func test<T:NSObject>(type:T, myfunc:(item: NSObject) -> Void) {
allfunc[NSStringFromClass(type.dynamicType)] = myfunc
}
Is this a compiler bug that you cannot downcast functions, or shouldn't it be possible anyway? Is there an alternative that I can use to create similar functionality?
Maybe the code in the playground looks a little strange. It's a modified extract from my project. If you want to see the complete project with the same crash, then have a look at https://github.com/evermeer/EVCloudKitDao
You can not cast functions. In the current Xcode version 6.2 you will get the following run time exception: Swift dynamic cast failure
There is however a workaround for this problem which I implemented in my connect function of https://github.com/evermeer/EVCloudKitDao The solution is to wrap the function instead of casting it. The code will look like this :
public var insertedHandlers = Dictionary<String, (item: EVCloudKitDataObject) -> Void>()
public var updateHandlers = Dictionary<String, (item: EVCloudKitDataObject, dataIndex:Int) -> Void>()
public func connect<T:EVCloudKitDataObject>(
type:T,
completionHandler: (results: [T]) -> Void,
insertedHandler:(item: T) -> Void,
updatedHandler:(item: T, dataIndex:Int) -> Void,
deletedHandler:(recordId: String, dataIndex:Int) -> Void,
errorHandler:(error: NSError) -> Void
) -> Void {
func insertedHandlerWrapper(item:EVCloudKitDataObject) -> Void {
insertedHandler(item: item as T)
}
func updatedHandlerWrapper(item:EVCloudKitDataObject, dataIndex:Int) -> Void {
updatedHandler(item: item as T, dataIndex: dataIndex)
}
self.insertedHandlers[filterId] = insertedHandlerWrapper
self.updateHandlers[filterId] = updatedHandlerWrapper
...
Now the updateHandler still uses the T instead of the EVCloudKitDataObject and in the handler itself you can use the original type and does not need to cast it.