How to check if NSDictionary is not nil in Swift 2 - ios

I'm getting NSDictionary as parameter in my function but having problem because don't know how to check if that parameter is not nil.
My function looks like this:
func doSmth(val : NSDictionary)
Inside my function I'm trying to get some values:
let action = val["action"] as! String
But getting error "fatal error: unexpectedly found nil while unwrapping an Optional value" when receive parameter val as nil.

The error is due to assuming (force casting) a value that can sometimes be nil. Swift is awesome, because it allows conditional unwraps and conditional casts in very concise statements. I recommend the following (for Swift 1-3):
Use "if let" to conditionally check for "action" in the dictionary.
Use as? to conditionally cast the value to a String
if let actionString = val["action"] as? String {
// action is not nil, is a String type, and is now stored in actionString
} else {
// action was either nil, or not a String type
}

You can also access the allKeys or alternatively allValues property and check if the array contains any elements like so:
let dic = NSDictionary()
let total = dic.allKeys.count
if total > 0 {
// Something's in there
}
else {
// Nothing in there
}
EDIT
Here is how you can detect if the NSDictionary is nil, if they key you are looking for exists, and if it does attempt to access it's value:
let yourKey = "yourKey"
if let dic = response.someDictionary as? NSDictionary {
// We've got a live one. NSDictionary is valid.
// Check the existence of key - OR check dic.allKeys.containsObject(yourKey).
let keyExists: Bool = false;
for var key as String in dic.allKeys {
if key == yourKey {
keyExists = true;
}
}
// If yourKey exists, access it's possible value.
if keyExists == true {
// Access your value
if let value = dic[yourKey] as? AnyObject {
// We're in business. We have the value!
}
else {
// yourKey does not contain a value.
}
}
else {
// yourKey does not exist in NSDictionary.
}
}
else {
// Call an ambulance. NSDictionary is nil.
}

That's not particularly related to Swift 2.
If the dictionary could be nil declare it as optional
func doSmth(val : NSDictionary?)
Then use optional bindings to check
if let valIsNonOptional = val {
let action = valIsNonOptional["action"] as! String
}
The code assumes that there is a key action containing a String value anyway if the dictionary is not nil

Your dictionary parameter is probably not nil. The problem is probably that your dictionary doesn't contain a value for the key "action".
When you say val["action"], the dictionary (being an NSDictionary) returns an Optional<AnyObject>. If val contains the key "action", it returns Some(value). If val doesn't contain the key "action", it returns None, which is the same as nil.
You can unwrap the Optional in your cast, and choose a course of action based on whether it was nil, using an if-let statement:
if let action = val["action"] as? String {
// action is a String, not an Optional<String>
} else {
// The dictionary doesn't contain the key "action", and
// action isn't declared in this scope.
}
If you really think val itself might be nil, you need to declare your function that way, and you can unwrap val without renaming it using a somewhat confusing guard statement:
func doSmth(val: NSDictionary?) {
guard let val = val else {
// If val vas passed in as nil, I get here.
return
}
// val is now an NSDictionary, not an Optional<NSDictionary>.
...
}

Related

How to check nil for variables in swift and avoid app crashing?

I'm having my string declared as,
var firstName = String()
and I'm assigning value from parsed JSON content like,
firstName = json["first_name"].stringValue
But sometimes, there might be empty values in the JSON response and the app is crashing, I read about guard statement and if statement to check empty values, but that requires the declaration format to be changed, couldn't find a right way to handle this error without changing the declaration format.
since I have declared all the variables in my app with similar formats, changing that requires time, I'm in the verge of uploading my app, this is my first swift app, if my declaration format is wrong please answer why it is, can someone help me out of this?
Code as of Swift 4:
Keep in mind that when you are using ".stringValue", it is almost the same as using a "!" which will force a crash on nil.
if let firstName = json["first_name"]as? String {
//do stuff like
self.firstName = firstName
}
This will unwrap it to where you can get at the value if it isn't null and can be a string.
Guard let's are really good for this though as you can account for it in the beginning and you can assume that it is not optional for the entire scope.
guard let firstName = json["first_name"]as? String else {return}
self.firstName = firstName
In addition, you could always check for nulls in one line and assign a default value if a nil value occurs.
self.firstName = (json["first_name"]as? String) ?? "Default String"
You can use next statement:
guard let firstName = json["first_name"].stringValue else { // Do sth if nil }
// Do sth if not nil
Or you could use statement, which you wrote, but you should check variable
firstName like this:
guard firstName != nil else { // Do sth if nil }
// Do sth if not nil
Or
if firstName != nil {
// Do sth if not nil
}
You can use guard statement also,
guard let firstName = json["first_name"] else {
print("FirstName is Empty")
return
}
or you can check with if also,
if let firstName = json["first_name"] {
//Your code goes here
}
You can do that the following way:
if let dictionary = json {
if let fName = dictionary["first_name"] {
firstName = fName as? String
}
}
I guest you use SwiftyJSON. The function stringValue always return String object. It's impossible to be nil. It sounds like the response data which is not valid JSON format, so it crashed.
My snippet codes.
// Alarmofire + SwiftyJSON
let request: DataRequest = ...//< Configure Alarmofire DataRequest
request.response() { (resp) in
if let error = resp.error {
// TODO: Error handle
return
}
if (response.data == nil) {
// TODO: error handle for empty data?
return
}
assert(response.data != nil, "In this place, the `data` MUST not be nil.")
let json = try? JSON(data: response.data!)
if (json == nil) {
// TODO: Error handle for invalid JSON format.
// If json is nil, then `json["first_name"]` will lead to crash.
return
}
// TODO: parse json object
let firstNameStr = json["first_name"].stringValue
}

Swift 3: Cant unwrap optional from Dictionary?

Ok I don't know what is going on here. I have a dictionary of Strings below:
var animals = ["max": "z", "Royal": nil] //store key pairs
and I am unable to print the value of the value in the key value pair without it printing "Optional" along with it.
I have tried using ! !! and casting as a String as well as the following:
var animalsToReturn = [String]()
if animals[selected]! != nil
{
if let pairName = animals[selected]
{
print("\(pairName)")
print("has pair",selected, animals[selected]!)
//trying to append to another array here
animalsToReturn.append("\(animals[selected]!)")
animalsToReturn.append(selected)
}
}
else {
print("no pair")
}
I check to make sure the value isn't nil, so it won't crash if I unwrap. But this is what is printed and the word Optional is appended to my other array:
You have included nil as a value, so the type of your dictionary's value is not String but Optional<String>. But fetching a value by key from a dictionary is itself an Optional. Therefore:
If your entry is present and is ultimately a String, it is an Optional<Optional<String>> and you have to unwrap it twice.
If your entry is present and is ultimately nil, it is an Optional wrapping nil.
If your entry is not present, it is nil.
You can readily test this as follows:
func test(_ selected:String) {
var animals = ["max": "z", "Royal": nil]
if let entry = animals[selected] { // attempt to find
if let entry = entry { // attempt to double-unwrap
print("found", entry)
} else {
print("found nil")
}
} else {
print("not found")
}
}
test("max") // found z
test("Royal") // found nil
test("glop") // not found
Contemplation of that example will answer your original question, namely "I don't know what is going on here".
animals[selected] is a Optional<Optional<String>> because you're storing nil. You can:
Double unwrap your value either by using if let or ! twice.
Change the type of your dictionary to [String: String] (instead of [String: String?]), and thus avoiding nil values.
Flatten the dictionary, removing nil values, and then accessing it as a [String: String]
You can flatten the dictionary using the code in this question.
Please enclose that in bracket and use double unwrapping. try this : -
animalsToReturn.append("\((animals[selected])!!)")
func addAnimal(_ animal: String) {
guard let animal = animals[animal] else {
print("No pair")
return
}
animalsToReturn.append(animal ?? "")
}

Closure argument becomes nil

I have a weird issue trying to validate user input. I'm using a wrapper for a form framework and I want it to be able to validate user input.
The trouble is, when I call the closure with the userValue argument, it ends up being nil and all checks return false:
class FormRowWrap {
var tag: String
var row: AnyObject
var verification: (value: Any?) -> Bool
init(tag: String, row:AnyObject, verification:(Any?) -> Bool) {
self.tag = tag
self.row = row
self.verification = verification
}
}
class InsertViewController: FormViewController {
let rows = [
{
let tag = "Fuel Name"
let row = FormRowWrap(tag: tag,
row:TextRow(tag) {
$0.title = tag
// $0.value = last known user default
},
verification:({(value: Any?) -> Bool in
if let thing = value as? String {
//^----- the value in a **breakpoint here is nil**
//
if !thing.isEmpty {
return true
}
}
return false
}))
return row
}() as FormRowWrap,
{
let tag = "Price"
let row = FormRowWrap(tag: tag,
...
func formValuesAreValid(values: [String: Any?]) -> Bool {
var result = false
for rowWrap in self.rows {
let userValue = values[rowWrap.tag]
print("userValue \(userValue) forTag: \(values[rowWrap.tag])")
// ^---- this prints userValue **Optional(Optional("Ghh")) forTag: Optional(Optional("Ghh"))**
let entryIsValid = rowWrap.verification(value: userValue)
if (!entryIsValid) {
result = false
return result
}
}
result = true
return result
}
If I run rowWrap.verification(value:"test") it returns true, so I think it's an issue about properly unwrapping values.
Your function needs an Optional but userValue is an Optional inside another Optional:
Optional(Optional("Ghh"))
So when you force unwrap it with
let entryIsValid = rowWrap.verification(value: userValue!)
what happens actually is that you unwrap the first layer and get back an Optional:
Optional("Ghh")
which is what your function signature
(value: Any?) -> Bool
needs.
About why it's wrapped twice:
with
formValuesAreValid(values: [String: Any?])
the values in the values dictionary are Optionals, and then when you access the dictionary:
let userValue = values[rowWrap.tag]
you get yet another Optional - because accessing a dictionary always returns an Optional, so in this case values[rowWrap.tag] returns an "Optional Optional".
Then somewhere else you unwrap once thinking you'll get the value if let thing = value as? String but you get the inner Optional instead and your next check fails.
Found a way to make it work by force unwrapping the "userValue":
let entryIsValid = rowWrap.verification(value: userValue!)
I still don't understand why this works and why it doesn't work with the argument as wrapped optional.

if statement ignores bool value in swift

I must doing something wrong, because inside the NSDictionary the values are 1 or 0 and it always prints "locked"...
let object: NSDictionary = self.collectionObjects?.objectAtIndex(indexPath.row) as! NSDictionary
if let locked = (object.objectForKey("locked") as? NSNumber)?.boolValue {
println("locked")
} else {
println("open")
}
Your not testing anything there
Try,
let object: NSDictionary = self.collectionObjects?.objectAtIndex(indexPath.row) as! NSDictionary
let locked = (object.objectForKey("locked") as? NSNumber)?.boolValue
if locked == true
{
println("locked")
}
else
{
println("open")
}
When you are doing:
if let locked = (object.objectForKey("locked") as? NSNumber)?.boolValue {
...
} else {
...
}
The if statement is checking whether or not your locked variable has evaluated to a nil value. If it has a value then the true statement block will be executed; if it has nil then the false block (else statement) will be executed.
This is called Optional Binding.
You use optional binding to find out whether an optional contains a
value, and if so, to make that value available as a temporary constant
or variable. Optional binding can be used with if and while statements
to check for a value inside an optional, and to extract that value
into a constant or variable, as part of a single action.
As a result, if your variable does not evaluate to nil then you could use the locked variable inside the if block.
For example:
if let locked = (object.objectForKey("locked") as? NSNumber)?.boolValue {
if locked {
println("locked")
} else {
println("open")
}
} else {
println("locked is nil")
}
See this SO post for an example.
What you're doing is unwrapping the conditional value, not checking it, so what you need to do is:
Unwrap it
Test it
Handily, the if-let-where statement can do it for us in one line like this:
if let object = self.collectionObjects?.objectAtIndex(indexPath.row) as? NSDictionary, locked = object.objectForKey("locked")?.boolValue where locked {
// we have a value here and it happens to be true
} else {
// this means we don't have a value
}
Include the first let object in the if statement too - you'll never know, it can be null and then it would unexpectedly crash.
Also, the compiler should be able to handle object -> boolValue directly, if not, add the NSNumber casting back.
I recommend this
let object: NSDictionary = self.collectionObjects?.objectAtIndex(indexPath.row) as! NSDictionary
if let locked = object.objectForKey("locked") as? NSNumber where locked.boolValue == true {
print("locked")
}else {
print("open")
}
You are checking only the value is whether nil or not nil. As the value is not nil you are always getting output as "locked".
Try this
let object: NSDictionary = self.collectionObjects?.objectAtIndex(indexPath.row) as! NSDictionary
if let locked = (object.objectForKey("locked") as? NSNumber)?.boolValue {
If locked == true {
println("locked")
}
else {
println("open")
}
}

How to extend the Swift Dictionary type to return a non-empty String or nil

I'm writing an extension to Dictionary so that when I give it a String key, it'll return me a String only if the value associated with the key is non-nil and not empty.
extension Dictionary {
subscript(key: String) -> String? {
if let string = super.subscript(key) {
if string.isEmpty == false {
return string
}
}
return nil
}
}
However, at the if let string = super.subscript(key) { line, I get the following compile error and I don't know what it means--neither is there a Google result that explains it:
Expected -> for subscript element type
I am doing this because I'm working with an API that returns a JSON where a key's value may be an empty string--which is an invalid value to the app by our requirements, and hence, as good as nil.
Of course the longer way works, but I'm looking for a way to make this shorter.
if let value = dict["key"] as? String {
if value.isEmpty == false {
// The value is non-nil and non-empty.
}
}
You're going to think this is very silly, but my suggestion would be: do more or less exactly what you're doing, but encapsulate it as a separate function rather than trying to deal with the implications of defining a new subscript:
extension Dictionary {
func nes(key:Key) -> String? {
var result : String? = nil
if let s = self[key] as? String {
if !s.isEmpty {
result = s
}
}
return result
}
}
(nes stands for "non-empty string".)
Now call it like d.nes("foo").

Resources