5

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)

T. Benjamin Larsen
  • 6,245
  • 4
  • 18
  • 30

1 Answers1

5

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
}
Martin R
  • 488,667
  • 78
  • 1,132
  • 1,248
  • It does not answer to the question - how to convert to NSDictionary. – Alexander Volkov Jan 21 '16 at 10:48
  • @AlexanderVolkov: I think it does: `if let problemDictionary = cfDict as NSDictionary? { ... }` casts the CFDictionary? to NSDictionary?, and uses optional binding to unwrap that to NSDictionary. Please let me know which information exactly you are missing. – Martin R Jan 21 '16 at 11:05
  • Take a look at the screenshot. The result of the cast is nil - http://www.screencast.com/t/hbjhDzCs0K – Alexander Volkov Jan 21 '16 at 11:23
  • @AlexanderVolkov: Did you try to replace `let options = attachments as? NSDictionary` by `let options = attachments as NSDictionary?`, as I suggested in my answer? – Martin R Jan 24 '16 at 16:35
  • @AlexanderVolkov: Any feedback? – Martin R Mar 28 '16 at 21:36
  • This worked for me (1 line): `let txtdict: CFDictionary = ["role":"Server"]` – xaphod Jul 08 '16 at 02:25
  • @xaphod i tried taking that approach and got an error `Contextual type 'CFDictionary' cannot be used with dictionary literal` – NSGangster Jul 27 '16 at 18:14