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

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)

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
}

Related

Swift correct approach to nil value

Reading about Optional values I was sure that all the bases were covered in my code, but I still get the dreaded unexpectedly found nil while unwrapping an Optional value.
That makes sense, since I've read: What does “fatal error: unexpectedly found nil while unwrapping an Optional value” mean?. It suggests making the Int optional, which is what I want:
func myCountUpdate(mainDict: [String : NSObject]) {
let myDict = mainDict["start"] as! [String : CFString]
let myCount = subDict["count"] as? String
let myTotal = Int(myCount)? // nope, it forces me to use non-optional !
// as the other thread suggest it's easy to check for nil with an optional int.
// how the hell can you do that if it won't allow you to make it optional?
if myTotal != nil {
print(myCount!)
let label: String = "\(myCount)"
text = label
} else {
text = nil
}
}
I've tried quite a bunch of things, including using other values to check for nil, etc. The issue is that the compiler will not allow me to declare the Int as non-optional, so what are my options? Xcode shows no warnings or suggestions on this issue, so maybe someone here has one - ty.
The best approach here is to use swift guards in order to check if a value is nil.
First, in the second line, where you use the subDict, its not referenced anywhere else, should it be myDict ?
The thing here is that the cast in let myCount = subDict["count"] as? String may be returning nil or there is not "count" in subDict. Therefore, when you do Int(myCount!), the force unwrapp of myCount is throwing the exception, since its nil.
You should avoid force unwrappings as much as you can, unless you are 100% sure that the value is not nil. In other cases, you should use the setting of a variable to check if it is not nil.
With your code, an updated version using guard would be the following:
func myCountUpdate(mainDict: [String : NSObject]) {
guard let myDict = mainDict["start"] as? [String : CFString],
let myCount = myDict["count"] as? String,
let myTotal = Int(myCount) else {
text = nil
return
}
print(myTotal)
let label: String = "\(count)"
text = label
}
This is safer, because if any of the conditions in the guard fails, then it's setting the text to nil an ending the method.
First unwrap the variable optional myCount(String?) to a variable called count (String).
let myCount = mainDict["count"] as? String
if let count = myCount {
//..
}
Then try to create a Int based on the variable count (String).
Which could return a nil since you could pass Int("Hi") or Int("1").
myTotal = Int(count)
Then after that you will have a variable called myTotal (Int?) with the result that you want.
Code
func myCountUpdate(mainDict: [String : Any]) {
let myDict = mainDict["start"] as? [String : Any]
if let myCount = myDict?["count"] as? String {
if let myTotal = Int(myCount) {
print(myTotal)
}
}
if let myCount = myDict?["count"] as? Int {
print(myCount)
}
}
Example 1
let data = [
"start": [
"count": "1"
]
]
myCountUpdate(mainDict: data) // outputs 1
Example 2
let data1 = [
"start": [
"count": 1
]
]
myCountUpdate(mainDict: data1) // outputs 1

How do I convert NSDictionary to Dictionary?

I have already updated to XCode 8 and now I need to convert my code from Swift 2 to Swift 3.
Before, when I want to convert NSDictionary to Dictionary, I just wrote the following:
let post_paramsValue = post_params as? Dictionary<String,AnyObject?>
where post_params is NSDictionary.
But now with Swift 3, I am receiving this error:
NSDictionary is not convertible to Dictionary
Why? What's changed?
Edit 1
I've also tried the following:
let post_paramsValue = post_params as Dictionary<String,Any>
But that gives this error:
Edit 2
I've also tried the following:
let post_paramsValue = post_params as Dictionary<String,Any>
Where I declare NSDictionary instead of NSDictionary!, but it doesn't work; I got this error:
Edit 3
I've also tried the following:
let post_paramsValue = post_params as Dictionary<String,Any>!
But I received this error:
NSDictionary in Objective-C has always non-optional values.
AnyObject has become Any in Swift 3.
Considering the first two "rules" NSDictionary can be bridge cast to Dictionary
let post_paramsValue = post_params as Dictionary<String,Any>
If the source NSDictionary is an optional you might use as Dictionary<String,Any>? or as? Dictionary<String,Any> or as! Dictionary<String,Any> or as Dictionary<String,Any>! depending on the actual type of the NSDictionary
For those who have a problem with NSDictionary, simply use this extension:
Swift 3.0
extension NSDictionary {
var swiftDictionary: Dictionary<String, Any> {
var swiftDictionary = Dictionary<String, Any>()
for key : Any in self.allKeys {
let stringKey = key as! String
if let keyValue = self.value(forKey: stringKey){
swiftDictionary[stringKey] = keyValue
}
}
return swiftDictionary
}
}
You just need to declare the NSDictionary properly in objc
For example: NSDictionary<NSString *, NSString*> gets translated automatically to [String: String] in swift interfaces

Init has been renamed to init(describing) error in Swift 3

This code works fine in Swift 2:
guard let userData = responseData["UserProfile"] as? [String : AnyObject] else { return }
var userProfileFieldsDict = [String: String]()
if let profileUsername = userData["Username"] as? NSString {
userProfileFieldsDict["username"] = String(profileUsername)
}
if let profileReputationpoints = userData["ReputationPoints"] as? NSNumber {
userProfileFieldsDict["reputation"] = String(profileReputationpoints)
}
But, in Swift 3 it throws an error on userProfileFieldsDict["reputation"] saying
init has been renamed to init(describing:)
My question is why does it trigger on that line and not on the userProfileFieldsDict["username"] assignment line, and how to go about fixing it? I'm assuming it's because I'm casting a NSNumber to a String, but I can't really understand why that matters.
NSNumber is a very generic class. It can be anything from a bool to a long to even a char. So the compiler is really not sure of the exact data type hence it's not able to call the right String constructor.
Instead use the String(describing: ) constructor as shown below
userProfileFieldsDict["reputation"] = String(describing: profileReputationpoints)
Here's more info about it.
You need to drop your use of Objective-C types. This was always a bad habit, and now the chickens have come home to roost. Don't cast to NSString and NSNumber. Cast to String and to the actual numeric type. Example:
if let profileUsername = userData["Username"] as? String {
userProfileFieldsDict["username"] = profileUsername
}
if let profileReputationpoints = userData["ReputationPoints"] as? Int { // or whatever
userProfileFieldsDict["reputation"] = String(profileReputationpoints)
}

NSData from CGImageRef in Swift

I am having difficulty figuring out how to get an NSData representation of an image from a CGImageRef. All of the answers I've found make use of UIImage or NSImage, but my application is cross-platform, so I want to use only Core Graphics. Objective-C answers state simply that CFData is toll-free bridged to NSData and simply cast it, but Swift will not allow this. The closest I've got is:
var image: CGImageRef? = nil
//...
if let dataProvider: CGDataProviderRef = CGDataProviderCreateWithURL(url) {
image = CGImageCreateWithPNGDataProvider(dataProvider, nil, false, CGColorRenderingIntent.RenderingIntentDefault)
// works fine
//...
if let data = CGDataProviderCopyData(CGImageGetDataProvider(image)) as? NSData {
// Do something with data, if only it ever got here!
}
}
but the cast doesn't ever succeed...
CGDataProviderCopyData() returns the optional CFData?, and that cannot be cast to the non-optional NSData. But you can convert/bridge
it to NSData? and use that in the optional binding:
if let data = CGDataProviderCopyData(CGImageGetDataProvider(image)) as NSData? {
// Do something with data ...
}
Here is a simpler example demonstrating the same issue with
CFString and NSString:
let cfstr : CFString? = "Hello world"
if let nsstr = cfstr as? NSString {
print("foo") // not printed
}
if let nsstr = cfstr as NSString? {
print("bar") // printed
}
But I admit that my explanation is not fully satisfying, because
a similar optional cast works in other cases:
class MyClass { }
class MySubclass : MyClass { }
let mc : MyClass? = MySubclass()
if let msc = mc as? MySubclass {
print("yes") // printed
}
So this must be related to the toll-free bridging between CoreFoundation
and Foundation types.

Swift - Why can I not return an NSNumber or Double data type from this NSDictionary object?

The second line of the code segment below returns an error unless I change the portion that reads "as NSNumber" to "as String". The value returned in rowData["lngID"] is a numeric value. Can someone please explain this to me?
let rowData: NSDictionary = objReport as NSDictionary
let lngReportID = rowData["lngID"] as NSNumber
What I'm actually attempting to do here is take a JSON response and load it into an array of objects as follows. Perhaps there is a better way to achieve this. Any suggestions for a better approach is much appreciated. First, the function didReceiveAPIResults returns the results to the app. Then the function loadReportsIntoArray is called.
func loadReportsIntoArray(pReports: NSArray) {
arrayPoints = []
for (intRow, objReport) in enumerate(pReports) {
// index is the index within the array
// participant is the real object contained in the array
let rowData: NSDictionary = objReport as NSDictionary
let lngReportID = rowData["lngID"] as NSNumber
let lngReportTypeID = rowData["lngTypeID"] as NSNumber
let strOtherTypeName = rowData["strOtherTypeName"] as String
let strDescription = rowData["strDescription"] as String
let dtmFirstReport = rowData["dtmFirstReport"] as String
let dblLat = rowData["dblLat"] as NSNumber
let dblLong = rowData["dblLong"] as NSNumber
let strReportedByUsername = rowData["strReportedByUsername"] as String
let lngReportedByID = rowData["lngReportedBy"] as NSNumber
let lngCommentCount = rowData["lngCommentCount"] as NSNumber
let lngNumLikes = rowData["lngNumLikes"] as NSNumber
let blnUserLikedEvent = rowData["blnUserLikedEvent"] as Bool
var objReport = Report(plngReportID: lngReportID, plngReportTypeID: lngReportTypeID, pstrOtherTypeName: strOtherTypeName, pstrDescription: strDescription, pdtmFirstReport: dtmFirstReport, pdblLat: dblLat, pdblLong: dblLong, pstrReportedByUsername: strReportedByUsername, plngReportedByID: lngReportedByID, plngCommentCount: lngCommentCount, plngNumLikes: lngNumLikes, pblnUserLikedEvent: blnUserLikedEvent)
//arrayPoints.append(objReport)
}
}
func didReceiveAPIResults(results: NSDictionary) {
var success: NSInteger = results["success"] as NSInteger
if success == 1 {
var resultsArr = results["geopoints"] as NSArray
dispatch_async(dispatch_get_main_queue(), {
self.loadReportsIntoArray(resultsArr)
})
}
else {
// Error occurred
}
}
I was able to recreate your error using the following code:
let objReport = NSDictionary(object: "string", forKey: "lngID")
let rowData: NSDictionary = objReport as NSDictionary
let lngReportID = rowData["lngID"] as NSNumber // Error
However, changing the objReport to NSDictionary(object: NSNumber(integer: 0), forKey: "lngID") solved the problem. Therefore, I think your problem is the object stored for the key lngID isn't an NSNumber.
For the solution to this you should look at Kumar Nitin's answer to check you've got a number stored, or you could use the code, they both do the same thing pretty much:
if let lngID = rowData["lngID"] as? NSNumber {
// Do stuff with lngID.
}
In swift, you don't have NSNumber, however you can use the Obj C's NSNumber if need be.
The above code for NSNumber should be as follows if you are expecting a double or float or int. Add a check to ensure the value is not nil, or else it will crash your app.
if let lngReportID = rowData["lngID"] as? Int {
//Do the task required
}