Horrible Coding Hack: Using C Function Pointers in Swift

Update: Swift 2.0 now supports function pointers, so this hack is no longer necessary at all. The information below remains here for historical purposes only.

Swift's interoperability with plain C and Objective-C code, while certainly improved over the original version, still is lacking in a few areas, notably the lack of a way to create C function pointers. This means that you are forced to add an Objective-C code file to your project in order to work with APIs that use C function pointers as callbacks. Or do you? It turns out there is a way to create a C function pointer from a block in Swift, without using Objective-C at all! ...Well, that's not really true, since we (horribly ab)use Objective-C runtime functions to do it, but you don't have to write Objective-C code to do this!

(Disclaimer: this hack is presented for informational purposes only. Since it uses the Objective-C runtime for purposes for which it's not really intended to be used, it's probably not suitable for actual production code. I take no responsibility if your karma is ruined, causing you to be reincarnated as a tree frog, as a result of actually using this code.)

Anyway, here's the code:

import Foundation let myBlock: @objc_block (DADisk!, UnsafeMutablePointer<Void>) -> Void = { (disk, ctxt) in let dict = DADiskCopyDescription(disk).takeRetainedValue() println("disk was inserted: \(dict)") } let myImp = imp_implementationWithBlock(unsafeBitCast(myBlock, AnyObject.self)) let callback = unsafeBitCast(myImp, DADiskAppearedCallback.self) let session = DASessionCreate(kCFAllocatorDefault).takeRetainedValue() DASessionScheduleWithRunLoop(session, CFRunLoopGetCurrent(), kCFRunLoopCommonModes) DARegisterDiskAppearedCallback(session, nil, callback, nil) CFRunLoopRun()

Compiling and running this code, and then inserting a disk, results in a notification being logged by the program. How does this work? It's quite simple: the imp_implementationWithBlock() function creates an IMP, or an Objective-C implementation, from an Objective-C block (the Obj-C counterpoint to a Swift closure). This IMP can then be used to add an instance method to a class at runtime, switch the implementation of an existing method, or really do whatever you want to hack away at the runtime. (Yes, Objective-C lets you do all kinds of gnarly stuff like this.) The thing, though, is that IMP is just a typedef to a function pointer:

typedef void (*IMP)(void /* id, SEL, ... */ );

However, it turns out that imp_implementationWithBlock() will turn any block, even one with a different signature than the above, into an IMP, which can then be cast to a function pointer with the block's signature. This allows you to create a function pointer completely in Swift, although via somewhat unsavory means.

Return to the main page