Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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)

like image 674
T. Benjamin Larsen Avatar asked Sep 22 '15 11:09

T. Benjamin Larsen


1 Answers

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
}
like image 108
Martin R Avatar answered Sep 30 '22 07:09

Martin R