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

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.

Related

How can I prevent this kind of logic error? (var x changes between its value is copied to another var y and the use of var y, so that y is outdated)

Summary:
I made a mistake in my Swift code and I've fixed it. Then I asked myself why this happened and how I could avoid it. I tried some ways but nothing helps.
I put the mistake and my thinking below. I hope you could teach me the right method to avoid this kind of mistake, but any idea, hint, or suggestion would be appreciated.
How can I avoid this kind of logic error?
The following is an excerpt from my assignment for Stanford cs193p course.
class SomeClass {
...
var accumulator: Double?
func someFunc() {
// I use `localAccumulator` and write `self.` for clarity.
if let localAccumulator = self.accumulator { // A
// `self.accumulator` is modified in this method
performPendingBinaryOperation() // B
pendingBinaryOperation = PendingBinaryOperation(firstOperand: localAccumulator) // C
}
}
private func performPendingBinaryOperation() {
accumulator = pendingBinaryOperation.perform(with: accumulator)
}
...
}
The problem here is that the line B has changed the value of instance value self.accumulator, and the line C should use the new value stored in self.accumulator, but it uses the outdated local var localAccumulator which was copied from self.accumulator's old value.
It was easy to find out the logic error via debugger. But then I reflected on my mistake, and was trying to look for a method to avoid this kind of logic error.
Method 1: use nil checking rather than optional binding
if self.accumulator != nil { // A
// `self.accumulator` is modified in this method
performPendingBinaryOperation() // B
pendingBinaryOperation = PendingBinaryOperation(firstOperand: self.accumulator!) // C
}
Actually, what really matters here is the force unwrapped self.accumulator!, it ensures the value comes from the real source. Using nil checking rather than optional binding can force me to force unwrap on self.accumulator.
But in some Swift style guides(GitHub, RayWenderlich, LinkedIn), force unwrapping is not encouraged. They prefer optional binding.
Method 2: use assertions.
if localAccumulator = self.accumulator { // A
// `self.accumulator` is modified in this method
performPendingBinaryOperation() // B
assert(localAccumulator == self.accumulator) // D
pendingBinaryOperation = PendingBinaryOperation(firstOperand: localAccumulator) // C
}
I insert a assertion to check whether the localAccumulator is still equal to self.accumulator. This works, it will stop running once self.accumulator is modified unexpectedly. But it's so easy to forget to add this assertion line.
Method 3: SwiftLint
To find a way to detect this kind of error, I've skimmed SwiftLint's all rules, and also got a basic understanding of
SourceKitten(one of SwiftLint's dependencies). It seems too complicated to detect this kind of error by SwiftLint, especially when I make this pattern more general.
Some similar cases
Case 1: guard optional binding
func someFunc() {
guard let localAccumulator = self.accumulator { // A
return
}
// `self.accumulator` is modified in this method
performPendingBinaryOperation() // B
pendingBinaryOperation = PendingBinaryOperation(firstOperand: localAccumulator) // C
}
In this case, it's much more difficult for human to notice the error, because localAccumulator has a broader scope with guard optional binding than if optional binding.
Case 2: value copy caused by function passing parameters
// Assume that this function will be called somewhere else with `self.accumulator` as its argument, like `someFunc(self.accumulator)`
func someFunc(_ localAccumulator) {
// `self.accumulator` is modified in this method
performPendingBinaryOperation() // B
pendingBinaryOperation = PendingBinaryOperation(firstOperand: localAccumulator) // C
}
In this case, localAccumulator copies from self.accumulator when this function is called, then self.accumulator changes in the line B, the line C expects the self.accumulator's new value, but get its old value from localAccumulator.
In fact, the basic pattern is as below,
var x = oldValue
let y = x
functionChangingX() // assign x newValue
functionExpectingX(y) // expecting y is newValue, but it's oldValue
x ~ self.accumulator
y ~ localAccumulator
functionChangingX ~ performPendingBinaryOperation
functionExpectingX ~ PendingBinaryOperation.init
This error pattern looks like so common that I guess there should be a name for this error pattern.
Anyway, back to my question, how can I avoid this kind of logic error?
This example shows what I understood from your problem:
var name: String? = "Mike"
func doSomething() {
// Check if "name" is set (not nil)
if let personName = name {
addLastNameToGlobalName() // modifies global var "name"
//
// Here, you want to use the updated "name" value. As you mention, at this point,
// "name" could/might/should be different than "personName" (it could
// even be nil).
//
greet(person: ???) // use "name"? (which is optional), "personName"?, or what?
}
}
To me, the general approach is the problem here. The fact that you are:
Checking that a global optional-value is not nil
Calling a function that alters this global optional-value
Wanting to use the updated global optional-value as a non-optional value
A simple change in the approach/design of greeting this person would allow you to avoid the type of "logic error" that you mention.
Example 1:
var name: String? = "Mike"
func doSomething() {
// Check if "name" is set (not nil)
if let personName = name {
greet(person: fullName(for: personName))
}
}
Example 2:
var name: String? = "Mike"
func doSomething() {
// Check if "name" is set (not nil)
if let personName = name {
let fullName = addLastNameToGlobalName() // modifies global var "name" and returns the new value
greet(person: fullName)
}
}

Conditional instructions in swift

I am trying to write in swift something that should be very basic, but I can't seem to get a handle on it :
First, I create a global variable. For example:
var xx:Int
Then, I want to create a conditional instruction. Something like :
if (xx == 1){
//do something
}
else if (xx == 2) {
//do something else
}
I can do this very easily in Objective-C, but I can't seem to be able to do it in Swift. I have looked everywhere, and don't seem to find the answer.
With the code you provided you're probably getting the error: "Variable xx used before initialized". This is happening because the declaration of the variable is incomplete, you neither gave a value to the variable nor told the compiler it is an optional. You have three options:
Give a initial value to it; var xx: Int = //value here
Declare it as an optional (doing this you say that it may not have a value, if it does the code will be executed, if it doesn't it won't); var xx: Int?
Force unwrap the variable (it still an optional, but if you force-unwrap it you're assuring the compiler that the variable will have a value when needed, otherwise it'll crash); var xx: Int!
Or you can say var xx = Int() that way it's initialized and the default initialization is equal to 0. This is different than the other answers and allows you to have a value from the get go if you're not sure what value might be assigned during runtime.
In addition to the other poster's point that you must assign an initial value before you can use xx, you also need to lose the parentheses around the condition in your if statement:
var xx:Int
xx = 2
if xx == 1
{
//do something
}
else if xx == 2
{
//do something else
}

Why can you assign non-optional values to optional types in Swift?

Why do assignments involving Swift optionals type check?
For example in,
var foo : Int? = 0
foo = foo!
foo and foo! do not have the same type. Shouldn't you need to wrap the unwrapped value to assign it to an optional type?
This is part of the syntactic sugar behind optionals. Assigning a non-optional value is how you wrap it in the optional type.
Since an optional indicates the presence or absence of a value, you shouldn't have to do anything special to indicate the presence of a value other than provide one. For example, in a function:
func gimmeSomethingMaybe() -> String? {
if arc4random_uniform(10) > 7 {
return "something"
} else {
return nil
}
}
Imagine if every time you wanted to return a real value from a function that's capable of returning nil, you had to write return Optional(value). That'd get old pretty fast, right? Optionals are an important feature of the language — even though they're actually implemented by the standard library, the syntactic sugar / automatic wrapping is there to keep it from being tedious to use them.
Edit: just to go a bit further into this... the sugar also helps to enforce the notion that a real value should not be optional. For example:
let one = 1
one? // error (in Swift 1.2, allowed but meaningless in Swift 1.1)
"two"? // error (ditto)
You can create an optional wrapping a real value with the Optional(one) initializer, but that has little semantic meaning on its own, so you almost never need to.
Optionals should come into play when there's "mystery" as to whether a value is present or absent — that is, when whether one part of a program receives a value (or no value) depends on state unknown to that part of the program. If you know you have a real value, there's no mystery... instead, you let the unknown come into play at the boundary between the code that knows the value and the code that doesn't know — that is, the function/method/property definition that hands that value off to somewhere.
After reading rickster's answer, I came up with a simple laymen terms answer. To me the whole whole gist of his answer is
Since an optional indicates the presence or absence of a value, you
shouldn't have to do anything special to indicate the presence of a
value other than provide one
An optional is an enum. Which has 2 cases a or b.
String?
|
An enum with 2 cases
|
a b
| |
Set notSet
| |
any value like "hi" nil
So you could do either of the things when you want to assign to an optional.
Say the value is either:
a: it's set to "hi"
b: it's not set, so it's nil
c: it just equals to another enum ie another optional
code:
var str : String?
var anotherOptional : String?
str = nil // nil <-- this is like case b
str = "hi" // "hi" <-- this is like case a
str = anotherOptional // nil <-- this is like case c
anotherOptional = "hi again"
str = anotherOptional // "hi again" <-- this is like case c

Swift: logical not operator not working

this piece of code prints two times 'false':
println(levelController?.died)
println(!levelController?.died)
I don't understand why, the levelController is instantiated and the died attribute is declared in LevelController like this:
var died = false
Can someone tell me what I might be doing wrong?
You can create your own not operator like this:
let not = (!)
And now we can assign a bool to test this:
let dead = false
if not(dead) {
print(dead)
// Code
} else {
// Code
}
This is Swift, not Objective C.
levelController?.died is not a boolean value. It is an optional boolean. It can be true, false, or nil. The first println prints false. The logical not operator, applied to an optional, returns false if the optional is not nil, and true if the optional is nil. Just as it does in C when applied to a pointer.
As a complete example, your code:
class Controller {
var died: Bool = false
}
var levelController: Controller? = Controller()
println(levelController?.died)
println(!levelController?.died)
Outputs the following:
Optional(false)
false
The first version of your code levelController?.died makes use of option chaining, unwrapping the levelController and accessing the died property. This explains why the output is Optional(false). You would typically use it as follows:
if let died = levelController?.died {
if (died) {
println("I died")
}
}
The if-let statements unwraps this optional value.
The second version of your code !levelController?.died tests whether the given optional value is nil or not. You will notice that changing died to true of false makes no difference.
However, changing the instantiation as follows:
var levelController: Controller? = nil
Results in !levelController?.died becoming true. This isn't really a terribly practical piece of code!

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.”