Search code examples
genericsswiftdowncast

Downcast of function call failing in Swift


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


Solution

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