swiftnsdictionaryswift2toll-free-bridgingcfdictionary

CFDictionary won't bridge to NSDictionary (Swift 2.0 / iOS9)


OK, this is a case I came across when working with CGImageSource and noticed that the toll-free-bridging between CFDictionary and NSDictionary seems to run into problems in certain cases. I've managed to construct the below example to show what I mean:

func optionalProblemDictionary() -> CFDictionary? {
    let key = "key"
    let value = "value"
    var keyCallBacks = CFDictionaryKeyCallBacks()
    var valueCallBacks = CFDictionaryValueCallBacks()

    let cfDictionary = CFDictionaryCreate(kCFAllocatorDefault, UnsafeMutablePointer(unsafeAddressOf(key)), UnsafeMutablePointer(unsafeAddressOf(value)), 1, &keyCallBacks, &valueCallBacks)
    return cfDictionary
}

Fairly straightforward (and a bit silly) but its a function returning and optional CFDictionary. The "fun" starts when trying to create an NSDictionary from this function:

Why won't the following work?

if let problemDictionary = optionalProblemDictionary() as? NSDictionary {
    print(problemDictionary) // never enters, no warnings, compiles just fine
}

While this works fine?

if let cfDictionary = optionalProblemDictionary() {
    let problemDictionary = cfDictionary as NSDictionary
    print(problemDictionary)
}

XCode 7.0 (7A220)


Solution

  • The reason seems to be that the function returns an optional CFDictionary? and that can not be cast to a (non-optional) NSDictionary.

    Here is a simpler example demonstrating the same problem with CFString vs NSString:

    let cfString = "foobar" as CFString?
    
    if let s1 = cfString as? NSString {
        print("s1 = \(s1)") // not executed
    }
    

    (The question remains why this does not give a compiler error or at least a compiler warning because this optional cast can never succeed.)

    But a casting to an optional NSString? works:

    if let s2 = cfString as NSString? {
        print("s2 = \(s2)") // prints "s2 = foobar"
    }
    

    In your case, if you change the "problematic case" to

    if let problemDictionary = cfDict as NSDictionary? {
        print(problemDictionary)
    }
    

    then the if-block is executed.


    Note that your method to build a CFDictionary in Swift is not correct and actually caused program crashes in my test. One reason is that the dictionary callbacks are set to empty structures. Another problem is that unsafeAddressOf(key) bridges the Swift string to an NSString which can be deallocated immediately.

    I don't know what the best method is to build a CFDictionary in Swift, but this worked in my test:

    func optionalProblemDictionary() -> CFDictionary? {
    
        let key = "key" as NSString 
        let value = "value" as NSString
    
        var keys = [ unsafeAddressOf(key) ]
        var values = [ unsafeAddressOf(value) ]
    
        var keyCallBacks = kCFTypeDictionaryKeyCallBacks
        var valueCallBacks = kCFTypeDictionaryValueCallBacks
    
        let cfDictionary = CFDictionaryCreate(kCFAllocatorDefault, &keys, &values, 1, &keyCallBacks, &valueCallBacks)
        return cfDictionary
    }