I have this code
let path = self.userDesktopDirectory + "/Library/Preferences/.GlobalPreferences.plist"
let dictRoot = NSDictionary(contentsOfFile: path)
if let dict = dictRoot{
try print(dict["AppleLocale"] as! String)
}
If the Value "AppleLocale" didnt exists the script crashes. What I must add to "catch" the Error and avoid the crash?
If the Value "AppleLocale" didnt exists the script crashes. What I
must add to "catch" the Error and avoid the crash?
depends on what's the reason for causing the crash. Mentioning that "If the Value AppleLocale didnt exists" means the the reason for the crash would be the force casting:
dict["AppleLocale"] as! String
probably, it has nothing to do with the try, it would be:
Unexpectedly found nil while unwrapping an Optional value
Means that at some point dict["AppleLocale"] could be nil or even if it contains a value as not a string it will crash (optional). You have to make sure that dict["AppleLocale"] is a valid (not nil) string, there are more than just one approach to follow for doing it, for instance you could do optional binding, like this:
let path = self.userDesktopDirectory + "/Library/Preferences/.GlobalPreferences.plist"
let dictRoot = NSDictionary(contentsOfFile: path)
if let dict = dictRoot{
if let appleLocale = dict["AppleLocale"] as? String {
print(appleLocale)
} else {
// `dict["AppleLocale"]` is nil OR contains not string value
}
}
Actually, I would assume that you don't have to deal with try for such a case.
Related
I am getting a weird result where URL(fileURLWithPath:) returns no value when called in a certain way. I've included code below (a simple macOS command line tool) to illustrate the problem.
I am using Xcode 10.3, on macOS 10.14.6. It also happens on iOS 12.4.1.
To see the problem, run the code below. Place a breakpoint on the return statement in the weirdStuff(withURL:) function, and on the 2nd-to-last line. Run the app, and when the code breaks on the return line, hover the pointer over the 4 lets in the function to examine their values. Here's what's weird:
badURL will not show any value at all! And the "Variables View" pane of the debug console will show that the value has not been defined.
copyOfBadURL will contain the correct value from badURL even though apparently badURL doesn't exist!
The conclusion is that URL(fileURLWithPath:) is failing (eg, badURL) when the fileURLWithPath: parameter value is a variable, but succeeds (goodURL) when the parameter value is a constant string. That can't be right!
Next, continue running the app so that you get the to other breakpoint. If you hover the pointer over url2 you'll see that the correct value was returned via return badURL. You can also see that badURLNowGood has a value as expected, even though this is essentially the same call that failed when called within my testing function.
What's going on here? I've never seen a variable never even be defined (after execution) before (ie, badURL).
import Foundation
func weirdStuff(withURL urlParam: URL) -> URL {
let pathFromParam = urlParam.path
let badURL = URL(fileURLWithPath: pathFromParam)
let goodURL = URL(fileURLWithPath: "/Dir1/file.txt")
let copyOfBadURL = badURL
print("func: badURL = \(badURL)") // is valid here but not if you hover above
print("func: copyOfBadURL = \(copyOfBadURL)")
print("func: goodURL = \(goodURL)")
return badURL // Breakpoint on this line; hover over 4 values above
}
// Call function with URL weirdness
let url1 = URL(fileURLWithPath: "/Dir1/file.txt", isDirectory: true)
let url2 = weirdStuff(withURL: url1)
print("main: weirdStuff(withURL:) result = \(url2)")
// Now do similar calls outside of the function
let pathFromURL1 = url1.path
var badURLNowGood = URL(fileURLWithPath: pathFromURL1)
let goodURLUsingString = URL(fileURLWithPath: "/Dir1/file.txt")
print("main: badURLNowGood = \(badURLNowGood)") // Breakpoint here
print("main: goodURLUsingString = \(goodURLUsingString)")
I am looking at the docs for
CFStringGetCString
and AXUIElementCopyAttributeValue.
CFStringGetCString takes the param buffer: UnsafeMutablePointer<Int8>!
AXUIElementCopyAttributeValue takes the param value: UnsafeMutablePointer<CFTypeRef?>
For the latter, I can do a call like this:
var value: CFTypeRef?
let err = AXUIElementCopyAttributeValue(element, attribute as CFString, &value);
This satisfies the doc asking for an UnsafeMutablePointer of type CFTypeRef.
However I can't get the same logic to apply by doing
let buffer: Int8!
CFStringGetCString(attribute as! CFString, &buffer, 2048, CFStringBuiltInEncodings.UTF8.rawValue)
I also tried
let buffer: Int8?
CFStringGetCString(attribute as! CFString, &buffer!, 2048, CFStringBuiltInEncodings.UTF8.rawValue)
Either way, it complains about using buffer before it's initialized, even though it never complained about value in the working method with similar param requirements.
All the working examples I've seen for CFStringGetCString are using objective-c like syntax with *. Not sure what the proper swift way is here.
I also tried this way to get the value I wanted:
let app = AXUIElementCreateSystemWide();
var valueString = "";
var value: CFTypeRef?
// An exception on execution happens here when passing app
// Passing in the element I clicked instead of app
// yields error -25205 (attributeunsupported)
let err = AXUIElementCopyAttributeValue(app, "AXFocusedApplication" as CFString, &value);
if (err == AXError.success) {
valueString = value! as! NSString as String;
} else {
print("ERROR!");
print(err.rawValue);
}
return valueString;
Why are you torturing yourself with CFStringGetCString? If you have a CFString in Swift, you can cast it to a String and get a C string from that:
let cString: [Int8] = (cfString as String).cString(using: .utf8)!
Note also that the value of the kAXFocusedApplicationAttribute is not a CFString. It is an AXUIElement.
Here's my playground test:
import Foundation
import CoreFoundation
let axSystem = AXUIElementCreateSystemWide()
var cfValue: CFTypeRef?
AXUIElementCopyAttributeValue(axSystem, kAXFocusedApplicationAttribute as CFString, &cfValue)
if let cfValue = cfValue, CFGetTypeID(cfValue) == AXUIElementGetTypeID() {
let axFocusedApplication = cfValue
print(axFocusedApplication)
}
The first time I executed this playground, I got a system dialog box telling me that I need to give Xcode permission to control my computer. I went to System Preferences > Security & Privacy > Privacy > Accessibility, found Xcode at the bottom of the list, and turned on its checkbox.
Here's the output of the playground:
<AXUIElement Application 0x7fb2d60001c0> {pid=30253}
I assume you're on macOS since the AXUI API is only available on macOS. If you just want the name of the front application as a string, you can do this:
if let frontAppName = NSWorkspace.shared.frontmostApplication?.localizedName {
print(frontAppName)
}
I'm parsing an NSDictionary. I don't want to go thru item by item to make sure everything is really there and nothing is unexpectedly nil. I figured I'd just do-try-catch. But I'm getting a compiler warning. Here's my code: saying:
do {
try adminMsg = NSDictionary(objects: [rawMsg["msg"]!["Title"]!!,
rawMsg["msg"]!["Text"]!!,
rawMsg["msg"]!["Time"]!!],
forKeys: ["Title", "Text", "Time"])
} catch {
adminMsg = nil
}
But I get this warning:
"no calls to throwing function occur within 'try' expression"
Does this mean I have no choice but to crash if an item is missing from the dictionary? I can't trap it and let my code gracefully tell the sender they sent me an invalid NSDictionary (unless I check it all out item-by-item in code)?
I can't trap it and let my code gracefully tell the sender they sent me an invalid NSDictionary
You cannot trap a fatal error, no. What you can do is catch the missing elements by using optional binding.
guard let title = message["Title"],
let text = message["Text"],
let time = message["Time"]
else {
// Missing data; inform caller
}
This is how you gracefully tell the client that there's something wrong with the data. You could return nil, an empty dictionary, or throw an error: whichever suits you best.
To make this properly Swifty, you should first define your data:
typealias RawMessage = Dictionary<String, [String : AnyObject]>
/** Important keys in a `RawMessage` */
enum RawMessageKey : String
{
case message = "msg"
case title = "Title"
case text = "Text"
case time = "Time"
}
enum RawMessageError : ErrorType
{
/** The `RawMessage` has no "message" key */
case NoMessage
/** The `RawMessage` is missing an expected inner key */
case MissingKey
}
Then your extraction function. This uses optional binding, not force unwrapping, to check that the key "msg" is present. Force unwrapping failure cannot be "caught"; that's not what it's for.
If the key is not present, you signal that to the caller by throwing your own error. Then use further optional binding to get the rest of the items. If any are missing, again throw an error.
/** Pull important values from `RawMessage` and repackage as `NSDictionary` */
func extractAdminMessage(rawMsg: RawMessage) throws -> NSDictionary
{
guard let message = rawMsg[String(RawMessageKey.message)] else {
throw RawMessageError.NoMessage
}
let keys = [String(RawMessageKey.title),
String(RawMessageKey.text),
String(RawMessageKey.time)]
guard let title = message[String(RawMessageKey.title)],
let text = message[String(RawMessageKey.text)],
let time = message[String(RawMessageKey.time)]
else {
throw RawMessageError.MissingKey
}
return NSDictionary(objects: [title, text, time],
forKeys: keys)
}
If you prefer, using flatMap could be an alternative to the stacked unbind:
let keys = [String(RawMessageKey.title),
String(RawMessageKey.text),
String(RawMessageKey.time)]
let objects = keys.flatMap { message[$0] }
guard objects.count == keys.count else {
throw RawMessageError.MissingKey
}
Here, any missing value will be dropped by flatMap; then if there aren't the same number of keys as objects, signal your caller.
I came up with this:
let rawMsg : NSDictionary =
["msg":["Title":"sometitle", "Text":"sometext", "Time":"sometime"]]
let adminMsg = NSMutableDictionary()
if let msg = rawMsg["msg"] as? NSDictionary {
for key in ["Title", "Text", "Time"] {
adminMsg[key] = msg[key]
}
}
That works even if a key is missing from the "msg" dictionary. You won't crash at any point during that, not matter how malformed rawMsg may be.
(It would be better if adminMsg were a Swift dictionary rather than a Cocoa NSDictionary, but at least this seems to cover the original problem domain.)
A few suggestions
Stop using NSDictionary and use Swift dictionary instead
Don't use exceptions to manage control flow
The force unwrap ! produce a fatal error, you cannot catch it
What you should do instead
if let
msg = rawMsg["msg"],
title = msg["Title"],
text = msg["Text"],
time = msg["Time"] {
let adminMsg:[String:Any] = ["Title": title, "Text": text, "Time": time]
}
I read a snippet from this POST but have not quite understood.
https://www.hackingwithswift.com/example-code/system/how-to-parse-json-using-nsjsonserialization
I am confused on some syntax in the following snippets.
In the following, I am not knowing why try goes here, it is strange syntax to me. Any information about try and as! for this expression?
let json = try NSJSONSerialization.JSONObjectWithData(data, options: []) as! [String: AnyObject]
In the following, I am not knowing what is as? [String] doing?
if let names = json["names"] as? [String] {
I know this question might be fundamental, I just need a keyword for me to search the related anwser, thanks. Here is my whole code block.
// define a string
let str = "{\"names\": [\"Bob\", \"Tim\", \"Tina\"]}"
// convert the string into NSUTF8StringEncoding
let data = str.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
// put the following statements in try catch block
do {
// not knowing why try goes here, strange syntax to me.
// any higher conception about try and as! for this expression
let json = try NSJSONSerialization.JSONObjectWithData(data, options: []) as! [String: AnyObject]
// json should be an object(dictionary, hash) use the key "names" to store string array into names
// not knowing what is `as? [String]` doing? any keyword for this syntax?
if let names = json["names"] as? [String] {
print(names)
}
} catch let error as NSError {
print("Failed to load: \(error.localizedDescription)")
}
// not knowing why try goes here, strange syntax to me. what if it fails? as! is what syntax, any keyword to search for it?
The try here is to basically to try to perform the function on that line. If it fails it will go to the catch. Which in this case will just print("Failed to load: \(error.localizedDescription)")
// json should be an object(dictionary, hash) use the key "names" to store string array into names // not knowing what is as? [String] doing? any keyword for this syntax?
The line that you are confuse about is performing a if else on the json object. It check for a key that is name and also want to ensure that its value is a String therefore the as?. In this case if the key name does not exist it will not met the condition. If the value of name is not a String it will not met the condition.
Like what #Martin R mention in the comments, you should read up more on Type Casting in Swift. This should help you. Explanation with some example if you have trouble with Apple Documentation.
As for try catch it is actually use in other languages as well not just Swift. You can read up more here.
I have some JSON data I'm working with on swiftyJSON. I'm putting it into core data. If all the fields are there, it works fine, but some of the fields are empty, and I'm trying to "protect" against the empty ones.
Here's the section of code that works just fine, if all fields are populated:
// Iterate through each item in the JSON
for (_,subJson) in readableJSON[]["data"] {
let title = subJson["title"].string!
let url_title = subJson["url_title"].string!
let entry_id = subJson["entry_id"].string!
let newPet: NSManagedObject = NSEntityDescription.insertNewObjectForEntityForName("Pets", inManagedObjectContext: context)
// Store the data in CoreData "Pets"
newPet.setValue(self.title, forKey: "title")
newPet.setValue(url_title, forKey: "url_title")
newPet.setValue(entry_id, forKey: "entry_id")
} // end of "for items in JSON struck
But, to try and protect against an empty field, I tried doing this to the JSON iteration:
if let entry_id = subJson["entry_id"].string {}
But when I do this, the newPet.setValue throw this error:
Use of unresolved identifier 'entry_id'
In this case, I know that entry_id always exists, so I'm confused.
Thoughts?
An if let statement only binds that variable within its own block:
if let entry_id = subJson["entry_id"].string {
//entry_id exists in this block, and is non-nil
}
else {
//entry_id was nil, doesn't exist
}
//entry_id doesn't exist anymore
If you would like to bind to that name for the parent scope, use guard let:
guard let entry_id = subJson["entry_id"].string else {
//entry_id was nil, doesn't exist
//you MUST break, return, or call a #noreturn function from here
}
//entry_id exists for the rest of this scope, and is non-nil