How to use guard to unrwap optionals in a dictionary? - swift

I am parsing a dictionary with stuff to generate a Rocket object. In the parsing method i use guard to do my checks. Is it possible to use guard to both check for existing value in a dictionary, and unwrap it at the same time?
let rocketDictionary : [String : String?] = [ "name" : nil, "numberOfThrusters" : nil ]
func generateRocketSchematics(rocketDictionary : [String : String?]) {
guard let rocketName = rocketDictionary["name"] as? String else {
print("no rocket name")
return
}
print(rocketName)
}
This code won't compile because of the as? String. If i remove that, the rocketName will be a String? Is it possible to write the guard statement so that it will return a String

You can use pattern matching with guard/case:
func generateRocketSchematics(rocketDictionary : [String : String?]) {
guard case let rocketName?? = rocketDictionary["name"] else {
print("no rocket name")
return
}
print(rocketName) // rocketName is a String
}
rocketName?? is a synonym for .Some(.Some(rocketName)), so
case let rocketName?? = rocketDictionary["name"]
matches the case where rocketDictionary["name"] (which has the
type String??) is not nil, and the unwrapped value (which has
the type String?) is also not nil. The doubly-unwrapped value
(of type String) is then assigned to rockedName.

Related

Retrieving Firebase Database value Swift Xcode

I have a lite problem, I cannot recover the carte value in my database. when i get there for username. But the path is good.
My code :
override func viewDidLoad() {
super.viewDidLoad()
loadCarte()
}
func loadCarte(){
var ref: DatabaseReference!
ref = Database.database().reference()
let userID = Auth.auth().currentUser?.uid
ref.child("users").child(userID!).observeSingleEvent(of: .value, with: { (snapshot) in
// Get user value
let value = snapshot.value as? NSDictionary
let Carte = value?["carte"] as? String ?? ""
self.carteButton.setTitle(Carte + " cartes", for: .normal)
// ...
}) { (error) in
print(error.localizedDescription)
}
}
I manage to recover other values but not that one, can that come from the fact that its being an Integer?
Thank's in Advance
EDIT : my Json structure of a User Type under users > userID
{
"carte" : 10,
"email" : "apperce.v#gmail.com",
"username" : "vicro87000"
}
You're storing a numeric value in the carte property, so you're casting a numeric value to a string. In Swift you can't cast a number to a string like that.
I've been told by a Swift expert that this is the best way to accomplish the same:
let carte = value?["carte"].flatMap { $0 as? Int } .map { String($0) }
The flatMap operation here unwraps the int value from inside the optional that it's in. Then the map converts the int value to a string. Calling the String initializer in the map closure is required because Swift Ints are not castable to String types.
Assuming you're structure is:
uid_0
"carte" : 10,
"email" : "apperce.v#gmail.com",
"username" : "vicro87000"
And you want to read in the child values, here's some code to read that node
let thisUser = ref.child("users").child(uid)
thisUser.observeSingleEvent(of: .value, with: { snapshot in
let email = snapshot.childSnapshot(forPath: "email").value as? String ?? "No email"
let userName = snapshot.childSnapshot(forPath: "username").value as? String ?? "No username"
let carte = snapshot.childSnapshot(forPath: "carte").value as? Int ?? 0
print(email, username, carte)
})
So the carte var will be an Int and can be used in a label
myTextField.text = "\(carte)"
or cast to a String
let myIntAsString = String(carte)
this
as? Int ?? 0
is called a nil coalescing operator and protects your code in case the value is set to nil, it actually is set to 0 so you're code doesn't crash.
As a side note, this is dangerous
userID!
as force-unwrapping an optional can lead to a crash
Better to do
guard let user = Auth.auth().currentUser else {return}
let uid = user.uid

Dynamic protocol conformance in Swift

Hi I am struggle to solve the problem dynamic protocol conformance in swift language. Please see code.
Protocol:
protocol Object {
init(by object: [String: Any])
}
Custom structs with protocol object conformance:
struct Tree: Object {
let treeName: String
init(by object: [String: Any]) {
self.treeName = object["tree"] as? String ?? "Notree"
}
}
struct Plant: Object {
let plantName: String
init(by object: [String : Any]) {
self.plantName = object["tree"] as? String ?? ""
}
}
The above code just fine until the object is [String: Any]. I can't use [[String: Any]] like below.
let coconut = ["tree":"Coconut"] // => This fine
let allTrees = [["tree":"Apple"],["tree":"Orange"],["tree":"Jakfruit"]] //=> Here is the problem
let aTree = Tree(by: coconut)
let bTree = Tree(by: ["data":allTrees])
let cTree = Plant(by: ["data":allTrees])
I can't use array of objects. So, I used to store objects in to key "data". Now I used extension: Array confirm protocol object.
extension Array: Object where Element == Object{
init(by object: [String : Any]) {
if let data = object["data"] as? [[String: Any]]{
self = data.map({ (object) -> Object in
// return Plant.init(by: object) // => Works, But I need dynamic confirmance
// return Tree.init(by: object) // => Works, But I need dynamic confirmance
return Object.init(by: object) //=> How can I do?
})
}else{
self = []
}
}
}
The return Object shows error Protocol type 'Object' cannot be instantiated. I tried lot to solve but not able.
Can someone suggest better idea or solution for this problem? Thank you in advance...
First, you should not use the constraint == Object. You want to say that not only [Object] is an Object, but also [Plant] and [Tree] are Objects too, right? For that, you should use the : Object constraint. Second, you can use Element.init to initialise a new Element of the array. Because of the constraint Element : Object, we know that a init(by:) initialiser exists:
extension Array: Object where Element: Object{
init(by object: [String : Any]) {
if let data = object["data"] as? [[String: Any]]{
self = data.map({ (object) in
return Element.init(by: object)
})
}else{
self = []
}
}
}
Usage:
let trees = [Tree](by: ["data": allTrees])
Here's what I think a more Swifty version of your code, making use of failable initialisers - initialisers that return nil when they fail to initialise the object:
protocol Object {
init?(by object: [String: Any])
}
struct Tree: Object {
let treeName: String
init?(by object: [String: Any]) {
if let treeName = object["tree"] as? String {
self.treeName = treeName
} else {
return nil
}
}
}
struct Plant: Object {
let plantName: String
init?(by object: [String : Any]) {
if let plantName = object["tree"] as? String {
self.plantName = plantName
} else {
return nil
}
}
}
extension Array: Object where Element: Object{
init?(by object: [String : Any]) {
if let data = object["data"] as? [[String: Any]]{
self = data.compactMap(Element.init)
}else{
return nil
}
}
}

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

enum method returning a dynamic type

I have an enum and I'd like to create a method to return a different type for every case.
For example, I have a dictionary [String: Any]. To process the values I'm using the enum to create an array of keys:
enum Foo {
case option1
case option2
func createKey() -> [String] {
switch self {
case .option1: return ["scenario1"]
case .option2: return ["scenario2"]
}
}
}
Once I have the values, I need to cast them to a the proper type to be able to use them. Right now I'm doing it manually using if-statements but it would reduce a lot of code if I can somehow create a method in the enum to return the proper type. My current code:
let origin: [String: Any] = ["scenario2": "someText"]
let option: Foo = .option2
option.createKey().forEach {
guard let rawValue = origin[$0] else { return }
switch option {
case .option1:
guard let value = rawValue as? Int else { return }
print("Value is an Int:", value)
case .option2:
guard let value = rawValue as? String else { return }
print("Value is a String:", value)
}
}
What I would like to achieve is something like:
option.createKey().forEach {
guard let rawValue = origin[$0] as? option.getType() else { return }
}
Is this possible?
I think the core of the problem here is that Swift has strict typing. That means types must be known at compile time. This, obviously, is legal:
let s : Any = "howdy"
if let ss = s as? String {
print(ss)
}
But this is not legal:
let s : Any = "howdy"
let someType = String.self
if let ss = s as? someType { // *
print(ss)
}
someType must be a type; it cannot be a variable hiding a type inside itself. But that is precisely what, in effect, you are asking to do.

How can you leverage Swift features to refactor this recursive function?

I've been working on a recursive function to extract String values out of JSON data represented as an NSDictionary. The function allows you to do this:
if let value = extractFromNestedDictionary(["fee" : ["fi" : ["fo" : "fum"]]], withKeys: ["fee", "fi", "fo"]) {
println("\(value) is the value after traversing fee-fi-fo");
}
And the function implementation looks like this:
// Recursively retrieves the nested dictionaries for each key in `keys`,
// until the value for the last key is retrieved, which is returned as a String?
func extractFromNestedDictionary(dictionary: NSDictionary, withKeys keys: [String]) -> String? {
if keys.isEmpty { return nil }
let head = keys[0]
if let result: AnyObject = dictionary[head] {
if keys.count == 1 {
return result as? String
} else {
let tail: [String] = Array(keys[1..<keys.count])
if let result = result as? NSDictionary {
return extractFromNestedDictionary(result, withKeys: tail)
} else {
return nil
}
}
} else {
return nil
}
}
Are there some syntactical features related to optional binding in Swift 1.2/2.x that can:
make this function more succinct
use less if nesting
Instead of recursion, you can use reduce on the keys array
to traverse through the dictionary:
func extractFromNestedDictionary(dictionary: NSDictionary, withKeys keys: [String]) -> String? {
return reduce(keys, dictionary as AnyObject?) {
($0 as? NSDictionary)?[$1]
} as? String
}
Inside the closure, $0 is the (optional) object on the current level and $1
the current key. The closure returns the object on the next level
if $0 is a dictionary and has a value for the current key,
and nil otherwise. The return value from reduce() is then
the object on the last level or nil.
I originally didn't want to just make it without you trying first, but I did it anyways because I love Swift and had fun doing it:
func extractFromNestedDictionary(dictionary: [NSObject : AnyObject], var withKeys keys: [String]) -> String? {
if let head = keys.first, result = dictionary[head] {
if keys.count == 1 {
return result as? String
} else if let result = result as? [NSObject : AnyObject] {
keys.removeAtIndex(0)
return extractFromNestedDictionary(result, withKeys: keys)
}
}
return nil
}
extractFromNestedDictionary(["A" : ["B" : ["C" : "D"]]], withKeys: ["A", "B", "C"])
A few notes:
Try to avoid NSDictionary and use [NSObject : AnyObject] instead, which can be bridged to NSDictionary anyways and is much more Swifty
When asking a question, try to make a better example than you did there, from your example I wasn't able to know what exactly you want to do.
I know this isn't strictly answering the question. But you could just use valueForKeypath:
let fum = dict.valueForKeyPath("fee.fi.fo")
Here's the best I came up with...
func extractFromNestedDictionary(dictionary: [NSObject : AnyObject], withKeys keys: [String]) -> String? {
if let head = keys.first,
result = dictionary[head] as? String
where keys.count == 1 {
return result
} else if let head = keys.first,
result = dictionary[head] as? [NSObject : AnyObject] {
return extractFromNestedDictionary(result, withKeys: Array(keys[1..<keys.count]))
}
return nil
}