Conditional if statement in Swift - swift

I came across a conditional if statement in Objective-C:
self.frontCardView = self.backCardView;
if ((self.backCardView = [self popPersonViewWithFrame:[self backCardViewFrame]])) {
// Fade the back card into view.
...
}
Basically:
if ((self.backCardView = self.popPersonViewWithFrame(self.backCardViewFrame()))) {...}
This sets "self.backCardView" to the return value of
"-popPersonViewWithFrame:". In C (and Objective-C), the result of an
assignment is the assigned value.
In this case, the result of the expression "(self.backCardView = [self
popPersonViewWithFrame:self.backCardViewFrame])" the return value of
"-popPersonViewWithFrame:".
If the return value is "nil", then the conditional is not executed (since "nil" is a false value).
If I try to do the same thing in Swift:
self.frontCardView = self.backCardView
if ((self.backCardView = self.popPersonViewWithFrame(self.backCardViewFrame()))) {
// Fade the back card into view.
...
}
I get an error in compilation:
Type '()' does not conform to protocol 'LogicValue'

Swift was specifically designed to not allow testing an assignment in a conditional for safety reasons (people accidentally using one = instead of two). The result of an assignment operator is always void () like the error says.

Assign, then check for nil separately.
self.frontCardView = self.backCardView
self.backCardView = self.popPersonViewWithFrame(self.backCardViewFrame())
if self.backCardView != nil {
// Fade the back card into view.
// ...
}

() is simply a typealias for void, which is what assignments return in Swift. As Bryan suggested, just put the assignment outside of the condition.

The condition isn't a condition, so like Bryan Chen said, do the assignment outside of the condition, but assign it to another variable. In the condition, then, check whether that variable is equal to backCardView, like so:
frontCardView = backCardView
let poppedView = self.popPersonViewWithFrame(self.backCardViewFrame())
if backCardView == poppedView {
// Fade the back card into view.
...

Related

Why outside of block swift can't see value assigned to a uninitialized variable in the block?

What is the mechanism of declaring w/o value in Swift5 ? Does the first assign become the real declaration ?
And, should we avoid to declare without value in Swift?
var v:String;
if true {
v = "Hello"
print(v) // print "Hello" when without the print below
}
print(v) // variable 'v' used before being initialized
var v:String="";
if true {
v = "Hello"
print(v) // print "Hello"
}
print(v) // print "Hello"
Well, the message is not very helpful, and that's the problem. This pattern (which I call computed initialization) is perfectly legal and useful and — amazingly — you can even use let instead of var. But you must initialize the uninitialized variable by all possible paths before you use it. So you have:
var v:String
if true {
v = "Hello"
}
print(v) // error
But hold my beer and watch this:
var v:String
if true {
v = "Hello"
} else {
v = "Goodbye"
}
print(v) // fine!
Or even:
let v:String
if true {
v = "Hello"
} else {
v = "Goodbye"
}
print(v) // fine!
Amazing, eh?
Now, you might say: OK, but true will always be true so it's silly to make me fulfill the "all paths" rule. Too bad! The compiler insists anyway, and then lets you off later with a warning that the else won't be executed. But a warning lets you compile; an error doesn't. The truth is that your example is very artificial. But this is a real-life possibility:
let v:String
if self.someBoolProperty {
v = "Hello"
} else {
v = "Goodbye"
}
print(v) // fine!
Not only is this sort of thing legal, it is actually the pattern that Apple recommends under certain slightly tricky circumstances. For instance, it is used in Apple's own example code showing how to use the Swift 5 Result struct:
let result: Result<Int, EntropyError>
if count < AsyncRandomGenerator.entropyLimit {
// Produce numbers until reaching the entropy limit.
result = .success(Int.random(in: 1...100))
} else {
// Supply a failure reason when the caller hits the limit.
result = .failure(.entropyDepleted)
}
So this is because swift compiles your code and notices that your value var v:String; is undeclared before being used which makes it an "optional" value. Even though you are assigning it within the if statement, if you were to get rid of the true value it is possible that the if statement would never run therefore no value will ever be stored in v, thus it would be used before "assigned".
So to answer your question if you want your value to be an optional and possible empty value declare v as the following var v:String? if you would like it to be a non-optional value with a value always stored within v you should declare it as the following var v = "". Swift will interpret this declaration as a String.
To answer your second question, defining without values in swift is 100% allowed, it really just depends on how you want to handle your code. I use optional values in my code all the time, but I don't necessarily like optionals so i usually like to declare my values such as var v = "" that way if the value is empty my UI or whatever else i'm manipulating won't break. But if i need to ensure a value is present i will have to make my value optional so i can use an if statement to check whether it's a valid value or not.
Shorter version of what I'm trying to say is, you are receiving the compiler warning because you are declaring v as a non-optional value rather than an optional value.

Is backed property safer than (lazy) in Swift?

Is it safer to use computed property backed by another property, than to use lazy modifier in Swift?
// method 1: using LAZY variable
lazy var myVar:Int {
// assume that this calculation takes
// a lot of time and resources
return 5
}
Now consider the following quote from Apple's Swift 2 (Pre-release):
NOTE
If a property marked with the lazy modifier is accessed by multiple
threads simultaneously and the property has not yet been initialized,
there is no guarantee that the property will be initialized only once.
Personally, I have been doing my own "lazy" logic like this:
Consider the alternative approach:
// method 2: using calculated property backed by another property
var myVar:Int? {
if self._myVar != nil {
return self._myVar
}
let aVar:Int = 5 // assume this is done after exhaustive calcuation
self._myVar = aVar
return self._myVar
}
var _myVar:Int? = nil
Using (method 2), do I get guarantee that the "exhaustive calculation" will only be executed once?
Your proposed method 2 does not guarantee anything that lazy does not.
// method 2: using calculated property backed by another property
var myVar:Int? {
if self._myVar != nil {
return self._myVar
}
let aVar:Int = 5 // assume this is done after exhaustive calcuation
self._myVar = aVar
return self._myVar
}
var _myVar:Int? = nil
Moreover, it has the added pitfall of being an optional rather than a non-optional. This would be slightly better as an implicitly unwrapped optional so we don't have to continuously unwrap it.
And, we can't set myVar. Plus _myVar should be private.
But the problem is that you're not locking the initialization code down to be synchronized.
Suppose you have thread A start to access myVar. The if self._myVar != nil check returns false, so we're not returning self._myVar. Now we enter the exhaustive calculation.
Now before exhaustive calculation completes, Thread B tries accessing myVar. Because Thread A has not completed exhaustive calculation yet, the if self._myVar != nil still returns false, and now Thread B does the exhaustive calculation.
Essentially, this is this the same problem that the lazy keyword gives you.... but you wrote a whole lot of code to do it that way when lazy would have sufficed.

Swift basic expression

I'm very new to swift, but proficient in other languages like Java, JavaScript, C, ... I'm lost with Swift syntax when it comes to create expressions. Look at this basic example where I just try to find out if one string is contained into another by calling String.rangeOfString that returns an Optional Range (Range?)
This works as expected:
let LEXEMA:String="http://"
let longUrl:String="http://badgirls.many/picture.png"
let range=longUrl.rangeOfString(LEXEMA);
if (range? != nil) {
// blah
}
Now I'm trying to combine the expression inside the if, something like:
if (longUrl.rangeOfString(LEXEMA)? !=nil) {
// blah
}
But I always get syntax errors, the above yields a "Expected Separator" and can't understand why. Done some more tests:
if (absolutePath.rangeOfString(URL_LEXEMA) !=nil) { }
Expected Separator before "!"
if absolutePath.rangeOfString(URL_LEXEMA) !=nil { }
Braced block of statements is an unused closure
What am I doing wrong?
If you’re coming from other like Java, you might be thinking of optionals like pointers/references, and so used to equating them to nil and if non-nil, using them. But this is probably going to lead to more confusion. Instead, think of them like a container for a possible result, that you need to unwrap to use. if let combines the test and unwrapping operation.
With this in mind, here’s how you could adapt your code:
let LEXEMA: String="http://"
let longUrl: String="http://badgirls.many/picture.png"
if let range = longUrl.rangeOfString(LEXEMA) {
// use range, which will be the unwrapped non-optional range
}
else {
// no such range, perhaps log an error if this shouldn’t happen
}
Note, that ? suffixing behaviour you were using changes in Swift 1.2 so even the code in your question that compiles in 1.1 won’t in 1.2.
It’s possible that sometimes you are whether there was a value returned, but you don’t actually need that value, just to know it wasn’t nil. In that case, you can compare the value to nil without the let:
if longUrl.rangeOfString(LEXEMA) != nil {
// there was a value, but you don't care what that value was
}
That said, the above is probably better expressed as:
if longUrl.hasPrefix(LEXEMA) { }
For starters:
You don't need parenthesis with if statements unless you have nested parenthetical subexpressions that require it.
You don't need to specify the type on the left side of the = of a let or var declaration if Swift can figure it out from the right side of the =. Very often Swift can figure it out, and you can tell that Swift can figure it out, so you can avoid that redundant clutter.
You do need to specify the type if Swift cannot figure out the type from
the right side. Example:
For example, consider the following lines:
let LEXEMA = "http://"
let longUrl = "http://badgirls.many/picture.png"
Swift can figure out that they're strings.
Similarly for this function or class that returns a UIView:
var myView = ViewReturningClassOrFunc()
Consider this:
#IBOutlet var myView : UIView!
In the above line, Swift cannot figure out ahead of time it will be assigned a UIView, so you have to provide the type. By providing a ! at the end you've made it an implicitly unwrapped optional. That means, like ?, you're indicating that it can be nil, but that you are confident it will never be nil at the time you access it, so Swift won't require you to put a ! after it when you reference it. That trick is a time saver and big convenience.
You should NOT add the ? to the line:
if (longUrl.rangeOfString(URL_LEXEMA) !=nil) {
As another answer pointed out, you're missing the let.
if let longUrl.rangeOfString(URL_LEXEMA) {
println("What do I win? :-)")
}
swift is case sensitive language. you need to check about whitespaces as well
if longUrl.rangeOfString(LEXEMA) != nil {
//your condition
}
there should be space between statement != nil
Just add a space between != and nil like:
if longUrl.rangeOfString(LEXEMA) != nil {
// blah
}
I tested your code in playground, an error of Expected ',' separator reported.
And do not forget the rules that 1s and 0s and Airspeed Velocity said.

Reason for assigning optional to new variable in conditional statement in Swift

I'm going through the swift docs, and in the optional segment, it talks about using the question mark -- ? -- to signify variables that might be nil. This can be used in an if statement to check for nil, but in the docs they assign the optional to a new variable in the conditional. Is there a reason for this?
For Example, it is presented in the docs similar to this:
// Declare an optional string (might be nil)
var optionalString: String? = "Hello"
// Assigns optionalString to new variable before checking if nil
if let string = optionalString {
println("\(optionalString) is not nil!")
}
else {
println("\(optionalString) is nil")
}
However, this runs just fine for me in tests:
var optionalString: String? = "Hello"
// Assigns optionalString to new variable before checking if nil
if optionalString {
println("\(optionalString) is not nil!")
}
else {
println("\(optionalString) is nil")
}
Question
Is there a reason to assign optionalString to a new variable string in the conditional statement?
Take a look at the section on Optional Chaining in the docs. In the example you cite, there's not much difference. But in other cases, an if-let construction lets you get at an unwrapped value that comes from a series of optional references and method calls, without using implicit unwraps that can crash your app if you haven't considered all the possible bindings for a value in a chain.
It's also useful if you want to avoid recomputing a value. You can use it in a lot of the same ways you'd use an assignment in a conditional in (Obj)C (remember if (self = [super init])).
For example, if the optional being tested comes from a computed property:
var optionalName: String? {
get {
if checkTouchID() {
return "John Appleseed"
} else {
return nil
}
}
}
var greeting = "Hello!"
if optionalName != nil {
greeting = "Hello, \(optionalName)"
}
Paste that into a playground, along with a stub implementation of checkTouchID() that returns true, and you'll immediately see in the results area that the optionalName getter is executing twice. (This would be a problem in a more realistic scenario, because you probably don't want code like this to implicitly checkTouchID() or downloadFromServer() or billApplePay() twice.) If you use an if-let construction instead, you'll only execute the getter once.
In a series of chained optionals (like if let johnsStreet = john.residence?.address?.street in the docs linked above), you don't want to rewrite the whole chain in the body of the if statement, much less recompute it.
I think the purpose of that assignment was to demonstrate the use of "let" within the if conditional clause. I don't see a meaningful difference between the provided code and your own.
From: Apple Inc. “The Swift Programming Language.” iBooks. https://itun.es/il/jEUH0.l
“If the optional value is nil, the conditional is false and the code in braces is skipped. Otherwise, the optional value is unwrapped and assigned to the constant after let, which makes the unwrapped value available inside the block of code.”

Using the result of an assignment as a condition without parentheses

I have this inside a custom UIActionSheet class
if (otherButtonTitles != nil) {
[self addButtonWithTitle:otherButtonTitles];
va_list args;
va_start(args, otherButtonTitles);
NSString * title = nil;
while(title = va_arg(args,NSString*)) { // error here
[self addButtonWithTitle:title];
}
va_end(args);
}
I have this error
! using the result of an assignment as a condition without parentheses
pointing to this line
while(title = va_arg(args,NSString*)) {
why is that?
thanks.
This is probably not an error as you said, it's a warning.
The compiler is warning you that you should surround assignments within parentheses when it is within a conditional to avoid the ol' assignment-when-you-mean-comparison mistake.
To get past this rather pedantic compiler warning, you can simply surround the assignment within another pair of parentheses:
while((title = va_arg(args,NSString*))) {
//...
}
It should be a warning and not an error. It's trying to warn in case you're using = but you meant ==.
In this case, you're not meaning to use == because you're calling va_arg() multiple times to iterate through otherButtonTitles and assigning it to the temp var title, so just add another set of parentheses. The warning will go away.
while((title = va_arg(args,NSString*))) {
compiler thinks that you're doing assignment by mistake, hence suggesting this error. You should wrap your statement into ONE MORE PAIR of parenthesis like this:
while((title = va_arg(args,NSString*)))
Then compiler first will evaluate the assignment, then the result of that assignment will be passed into the while condition
It does seem a little picky, but have you tried:
while((title = va_arg(args,NSString*)) != NULL)
?