Reputation: 3377
Town struct:
struct Town {
var population: Int = 5_422
var numberOfStoplights: Int = 4
func printDescription() {
print("The town has \(population) people and \(numberOfStoplights) stoplights")
}
mutating func changePopulation(by amount: Int) {
population += amount
}
}
Monster class:
class Monster {
var town: Town?
var name = "Monster"
func terrorizeTown() {
if town != nil {
print("\(name) is terrorizing a town")
} else {
print("\(name) hasn't found a town to terrorize yet")
}
}
}
Zombie class:
class Zombie: Monster {
var walksWithLimp = true
final override func terrorizeTown() {
if town?.population > 0 {
town?.changePopulation(by: -10)
}
super.terrorizeTown()
}
}
I'm getting a compiler error on town?.population
saying 'value of optional Int not unwrapped...'. I haven't declared population
to be an optional and if I change this to town?.population?
it says population is a non-optional Int. Both these errors contradict each other, what's going on?
Upvotes: 0
Views: 427
Reputation: 73176
Consider the following example:
struct Foo {
let bar: Int // not: not an optional
init(_ bar: Int) { self.bar = bar }
}
let foo: Foo? = Foo(42) // 'foo' is an optional
In case we are not interested particularly in foo
, but rather specifically in its member bar
, we may use optional chaining to query access to bar
of foo
, which will give us the value of bar
wrapped in .some(...)
in case foo
is not nil
, and nil
otherwise. I.e., such a query (even to a non-optional property as `bar) will yield an optional:
let optBar = foo?.bar // this is now an optional (Optional<Int>)
To reflect the fact that optional chaining can be called on a
nil
value, the result of an optional chaining call is always an optional value, even if the property, method, or subscript you are querying returns a nonoptional value.
Before we proceed, we also note that in Swift 3 we may no longer compare optional operands by the Comparable
operators, as stated in the following accepted and implemented Swift evolution proposal:
Hence, if you'd like to conditionally proceed into an if
block given the following:
foo
is not nil
, andbar
is larger than 0
Then you first need to, in some manner, achieve a concrete value of (the possibly existing) bar
prior to comparing it to 0
.
You can do this e.g. a combined optional binding and boolean conditional if
statement:
if let nonOptionalBar = foo?.bar, nonOptionalBar > 0 {
print("'foo' is not nil and its 'bar' member is larger than 0")
} // prints "'foo' is not nil and its 'bar' member is larger than 0"
If you don't plan to use the value bar
, however (and only wants to decrease it's population), you could make use of the nil
coalescing operator to construct a conditional if
statement that will fail given that foo
is nil
or its member bar
is not greater than 0:
if (foo?.bar ?? -1) > 0 {
print("'foo' is not nil and its 'bar' member is larger than 0")
}
Here, (foo?.bar ?? -1)
will result in -1
if foo
is nil
(which will subsequently result in false
for -1 > 0
), or result in the value of bar
(as a non-optional concrete Int
) in case foo
is not nil
.
We could also make use of the map
method of Optional
to conditionally (that foo
is not nil
) test the comparison, and if foo
is nil
, supply false
as a default value using the nil
coalescing operator on the return from the call to map
.
if foo.map({ $0.bar > 0 }) ?? false {
print("'foo' is not nil and its 'bar' member is larger than 0")
}
The three alternatives applied to your example:
if let population = town?.population, population > 0 {
town?.changePopulation(by: -10)
}
if (town?.population ?? -1) > 0 {
town?.changePopulation(by: -10)
}
if town.map({ $0.population > 0 }) ?? false {
town?.changePopulation(by: -10)
}
Upvotes: 2
Reputation: 5823
You must initialise instance of you structure Town, otherwise it is always return nil. So I see your code, Town? is always return nil.
Upvotes: 0
Reputation: 715
Because town?.population is still Int?, and you can't compare Int? with Int unless you get the Int from Int?
So the code could be
if let town = town, town.population > 0 {
town.changePopulation(by: -10)
}
super.terrorizeTown()
Upvotes: 0
Reputation: 8819
The expression returns an optional Int
but you are still testing it using non-optional syntax. You need to use if let
.
if let pop = town?.population {
if pop > 0
etc.
The optional chain does not remove the optionality. It simply cascades it so that any nil in the chain causes a nil to pop out at the end. But the result is still optional.
Upvotes: 0