Using guard with a non-optional value assignment - swift

This is not a question about optional arrays, as one can see in the answers.
I like using guard because it makes your intensions clear. I've used it both for the optional version like this...
guard let c = MyOptionalArray else { return }
as well as for more traditional bounds checking on non-optionals...
guard MyArray.count > 0 else { return }
But now I'd like to use that count in following code. So I did...
guard let c = MyArray.count > 0 else { return }
which doesn't work, obviously, so I did what should...
guard let c = parts.count where c > 1 else { return }
But that says Initializer for conditional binding must have Optional type, not 'Int'. Now I understand the error, and tried a bunch of seemingly obvious changes to the format, but no go. Is there no way to use guard as an assignment on a non-optional value? This seems like something it should be able to do.

If you throw a case in there, it'll work. So as follows:
guard case let c = parts.count where c > 1 else { return }

You can initialize an optional wrapping the non-optional:
guard let count = Optional([42].count), count > 0 else {
return
}
guard let count = .some([42].count), count > 0 else {
return
}
or cast it to an optional:
guard let count = [42].count as Int?, count > 0 else {
return
}
As mentioned in other answers, guard case let also works:
guard case let count = [42].count, count > 0 else {
return
}
guard case let count = [42].count where count > 0 else {
return
}

Related

guard let with logical operators

How can I use guard let like:
guard let value = vm.value1 || let value = vm.value2 else { return }
I need to check value1, If it has value, continue to work with it, else check value2, and work with it, else: quit. Only one can have value.
The semantics you are describing seems to be:
guard let value = vm.value1 ?? vm.value2 else { return }
If vm.value1 is not nil, value would be bound to its value, and the code after the guard statement would be executed.
Otherwise, if vm.value2 is not nil, value would be bound to its value, and the code after the guard statement would be executed.
Otherwise, return would be executed.
Similarly, multiple lets could be used to achieve something similar to the semantics of the logical "AND":
guard let value1 = vm.value1, let value2 = vm.value2 else { return }
The code after the guard statement is only executed if vm.value1 is not nil, and vm.value2 is not nil, and value1 and value2 are bound to the corresponding values.
Also note that you can mix arbitrary boolean conditions with the let bindings too:
guard let value = vm.value1, someBool || someOtherBool else { return }
You can't use logical operator with guard statement
But there is another way of performing AND operator
guard let value = vm.value1,
let value = vm.value2 else {
return
}
And OR operator functionality can be achieve by using ternary operator with guard statement
guard let value = ((vm.value1 != nil) ? vm.value1 : vm.value2 else {
return
}
And you can use the value after else statement

How to elegantly combine a guard statement with a condition?

I currently have the guard statement:
guard let designationQuota = Defaults.quotas.value?.designationQuota, designationQuota > 0 else {
return AppDelegate.shared.presentNoDesignationQuotaWarning()
}
however I only want to do the guard block if the variable needsQuota == true. I want to skip the guard statement if needsQuota == false. Is there a nicer way of doing this over than an if statement with a return?
EDIT:
How do I simplify this into a single guard?
if needsQuota {
guard let designationQuota = Defaults.quotas.value?.designationQuota, designationQuota > 0 else {
return AppDelegate.shared.presentNoDesignationQuotaWarning()
}
}
How about :
guard !needsQuota ||
(Defaults.quotas.value?.designationQuota.map { $0 > 0 } == true) else {
return AppDelegate.shared.presentNoDesignationQuotaWarning()
}
The problem is that you want to continue execution differently in case your if condition fails or in case your guard fails, so you cannot really combine them into a single guard. However, you could combine the two conditions into an if statement by putting the negated version of your guard condition in the if statement.
if needsQuota && (Defaults.quotas.value?.designationQuota ?? 0 <= 0) {
return AppDelegate.shared.presentNoDesignationQuotaWarning()
}
Wouldn‘t this do the trick?
guard needsQuota, let designationQuota = Defaults.quotas.value?.designationQuota, designationQuota > 0 else {
return AppDelegate.shared.presentNoDesignationQuotaWarning()
}

Optional variable force unwrapping that I haven't seen a solutions for

Force unwrapping is evil so I'm using guard or if let where I can, but sometimes this is really giving me a headache.
1 based list index variable to 0 based array in a function
//index is an integer from a 1 based list, so it can be 1, 2, 3,... (index is coming from an external API)
func someFunction (index : Int?) {
guard let newIndex = index - 1 else {
throw Abort.custom(status: .badRequest,
message: "blah blah")
}
let result = myArray[newIndex]
....
}
The editor flags an error "
Value of optional type Int is not unwrapped, did you mean to use ! or
?"
# index in the line : guard let newIndex = index - 1 else {
Adding a ! to index results in another error :
"Initializer for conditional binding must have Optional type, not int"
So the solution I'm currently using is :
guard var newIndex = index else {
throw Abort.custom(status: .badRequest,
message: "blah blah")
}
newIndex -= 1
let result = myArray[newIndex]
....
It works but I think it's kinda ugly coding...
You could use a method invocation rather than an operator for subtraction:
func someFunction (index : Int?) {
guard var newIndex = index?.advanced(by: -1) else {
throw Abort.custom(status: .badRequest,
message: "blah blah")
}
let result = myArray[newIndex]
....
}
Which does not answer the question in general, but in your specific case.
The safest way is to use optional binding to unwrap the index and then check if the (1-based) index is in the range of the number of items in the array (~= is the pattern matching operator)
guard var newIndex = index, // unwraps the optional
1...myArray.count ~= newIndex // is index in the range 1 ... number of items in the array
else {
throw Abort.custom(status: .badRequest,
message: "blah blah")
return }
newIndex -= 1
let result = myArray[newIndex]
The valid index range is
0-based : 0..<myArray.count
1-based : 1...myArray.count
If the check succeeds decrement the index and get the item.
Or still simpler if newIndex is not used later:
guard let newIndex = index, // unwraps the optional
1...myArray.count ~= newIndex // is index in the range 1 ... number of items in the array
else {
throw Abort.custom(status: .badRequest,
message: "blah blah")
return }
let result = myArray[newIndex - 1]
For your particular case you can do something like:
func someFunction (index : Int?) throws {
guard let newIndex = Optional( (index ?? 0) - 1 ), newIndex >= 0 else {
throw Abort.custom(status: .badRequest, message: "blah blah")
}
let result = myArray[newIndex]
....
}
By (index ?? 0), you are telling if index isn't nil then index = index! otherwise 0
So you have your unwrapped value and you subtract 1 from it. But you have to use Optional type for optional binding. So make it optional by Optional( (index ?? 0) - 1 )
By now, you've got your newIndex and by providing newIndex >= 0 check you are confirming that you won't do anything with negative index values.
So you can convert 1 based list index variable to 0 based array in a function
guard let newIndex = index - 1
guard let is only used for unwrapping an Optional. Here, you are confusing the action of unwrapping, and the action of removing 1 to your integer.
You were correct to do that in two steps, not one. And it is not ugly at all!
For example :
func someFunction (index : Int?) throws {
guard let unwrappedIndex = index else {
//The given index is nil, abort.
throw Abort.custom(status: .badRequest,
message: "Index is nil")
}
//Now everything is fine, unwrappedIndex is not an Optional anymore.
guard unwrappedIndex - 1 > 0 else {
throw Abort.custom(status: .badRequest,
message: "Wrong index")
}
//Happy scenario... Do you things...
let result = myArray[newIndex]
...
}
And even better, you are not forced to declare another variable name to unwrap it! Just do a guard like :
guard let index = index else { throw Error }

finding a number in a range swift 3

I have a number that I get from JSON, this number represents an age. Users give me a range of two ages and may code is supposed to check if this number I'm getting from JSON is in the range.
here is my code and it gives me error
Type of Expression is ambiguous without more context?
let age = "40"
if Int(AgeFrom) ... Int(AgeTO) ~= Int(age) {
print("yes")
}
Update
if let value: AnyObject = response.result.value as AnyObject? {
var ages = String
let json = JSON(value)
for (key, subJson) in json {
ages.append(subJson["age"].string!)
}
guard let min = Int(self.DropDownFrom.selectedItem!) else { return }
guard let max = Int(self.DropDownTo.selectedItem!) else { return }
for fitage in ages {
switch ages
{
case (min...max):
print ("Age is in range")
default:
print ("Nope, not this time")
}
}
Still gives me an error.
You need to unwrap the optionals because the Int(:String) method might not have a valid answer.
Best way to do this is kind of thing is with guard
guard let min = Int(AgeFrom) else { return }
guard let max = Int(AgeTo) else { return }
And from there you can go with the simple if statement:
if (min <= age && age <= max)
{
print ("Age is in range")
}
or get really fancy and use the switch statement pattern matching syntax (which I much prefer)
switch age
{
case (min...max):
print ("Age is in range")
default:
print ("Nope, not this time")
}
if - simplicity and readability
It is a basic thing in programming, checking if an optional is between two other optional values with an if:
if Int(AgeFrom)! <= Int(age)! && Int(AgeTO)! >= Int(age)! {
print("It is in the range!")
}
switch - multiple cases handling
However, I recommend using a switch for case handling:
switch(Int(AgeFrom)! <= Int(age)!, Int(AgeTO)! >= Int(age)!){
case (true,true): print("Yes, it fits the range")
case (false,true): print("Too young!")
case (true,false): print("Too old!")
}
The second solution is far better for multiple cases of the age value, especially when it's outside the range.
Hope it helps!
You can also use optional binding:
if let ageFrom = Int(ageFrom),
let ageTo = Int(ageTo),
ageFrom...ageTo ~= age
{
print("yes")
} else {
print("no")
}
You have to unwrap the optionals:
if Int(AgeFrom)!...Int(AgeTO)! ~= Int(age)! {
print("yes")
}
of course that is the unsafe way of unwrapping, since it will crash if the conversion of AgeFrom, AgeTO or age fail.

Unwrapping optional inside of closure using reduce

I have a quick question that is confusing me a little bit. I made a simple average function that takes an array of optional Ints. I check to make sure the array does not contain a nil value but when I use reduce I have to force unwrap one of the two elements in the closure. Why is it that I only force unwrap the second one (in my case $1!)
func average2(array: [Int?]) -> Double? {
let N = Double(array.count)
guard N > 0 && !array.contains({$0 == nil}) else {
return nil
}
let sum = Double(array.reduce(0) {$0+$1!})
let average = sum / N
return average
}
I know it is simple but I would like to understand it properly.
The first parameter of reduce is the sum, which is 0 in the beginning. The second one is the current element of your array which is an optional Int and therefore has to be unwrapped.
Your invocation of reduce does this:
var sum = 0 // Your starting value (an Int)
for elem in array {
sum = sum + elem! // This is the $0 + $1!
}
EDIT: I couldn't get a more functional approach than this to work:
func average(array: [Int?]) -> Double? {
guard !array.isEmpty else { return nil }
let nonNilArray = array.flatMap{ $0 }
guard nonNilArray.count == array.count else { return nil }
return Double(nonNilArray.reduce(0, combine: +)) / Double(nonNilArray.count)
}
You can also discard the second guard if you want something like average([1, 2, nil]) to return 1.5 instead of nil