I grasp (I think) the basics of optional types in Swift and roughy understand the difference between ?
and !
, but I'm still mystified by some of the results I get when I use these features — in particular the role of Some <T>
, and how it differs from <T>
itself; some of the specific error messages I get in certain cases; and how Some <T>
seems to pop up in cases where I expect <T>
.
But I also feel like even when I understand individual cases, my grasp of the picture gets away from me, and I feel like there is a code here that I could decipher if only I completely understood one simple example — a Rosetta Stone, if you will — for !
, ?
, optional values, and unpacking.
Here, for example, is a simple and (I think) exhaustive catalog of the basic cases:
class Foo {
var one:String = "";
var two:String?
var three:String!
}
let test = Foo() // {one "" nil nil}
test.one
//test.one? // ERROR: ? requires optional type
//test.one! // ERROR: ! requires optional type
// ?, unassigned
test.two // nil
test.two? // nil
//test.two! // ERROR: EXEC_BAD_INSTRUCTION
test.two == nil // true
test.two? == nil // true
//test.two! == nil // ERROR: Cannot invoke == with an argument list of type (@lvalue String, NilLiteralConvertable)
//test.two.isEmpty // ERROR: String? does not have .isEmpty
test.two?.isEmpty // nil
//test.two!.isEmpty // ERROR: EXEC_BAD_INSTRUCTION
// !, unassigned
test.three // nil
test.three? // nil
//test.three! // ERROR: EXEC_BAD_INSTRUCTION
test.three == nil // true
test.three? == nil // true
//test.three! == nil // ERROR: Cannot invoke == with an argument list of type (@lvalue String, NilLiteralConvertable)
//test.three.isEmpty // ERROR: EXEC_BAD_INSTRUCTION
test.three?.isEmpty // nil
//test.three!.isEmpty // ERROR: EXEC_BAD_INSTRUCTION
test.two = "???" // {one "" {Some "???"} nil}
test.three = "!!!" // {one "" {Some "???"} three "!!!"}
// ?, assigned
test.two // {Some "???"}
test.two? // {Some "???"}
test.two! // "???"
test.two == nil // false
test.two? == nil // false
//test.two! == nil // ERROR: Cannot invoke == with an argument list of type (@lvalue String, NilLiteralConvertable)
//test.two.isEmpty // ERROR: String? does not have .isEmpty
test.two?.isEmpty // {Some false}
test.two!.isEmpty // false
// !, assigned
test.three // "!!!"
test.three? // {Some "!!!"}
test.three! // "!!!"
test.three == nil // false
test.three? == nil // false
//test.three! == nil // ERROR: Cannot invoke == with an argument list of type (@lvalue String, NilLiteralConvertable)
test.three.isEmpty // false
test.three?.isEmpty // {Some false}
test.three!.isEmpty // false
If someone could annotate this, explaining what's going on it each case, I think that answer could serve as a solid reference for how these features of Swift work.
Alright, I am going to try to answer this all. Might not have tie to get through everything:
NOTE: Feel free to call me out on errors. This took a while, so I surely made a few.
Quick note: Optional
is actually an enum. It has two states: .None
, and .Some(T)
, where T
is the type of the value (in your case, String
).
test.one
Foo
has a property named one
that returns an String
. A definite String
, not an optional, meaning that it will definitely have a value. You treat this similarly to how you would just write "HI!"
in your code. The value of this is really ""
//test.one? // ERROR: ? requires optional type
This is an error because test.one
, as said above, returns a definite String
, and so there is no chance that it is nil. You can guarantee that the return value exists.
//test.one! // ERROR: ! requires optional type
Same as the ?. The ! is a forced unwrapping operator, meaning that there is a chance that test.one may be nil, but you want to force the value out anyway (or crash if it is not there). However, there is no chance it is nil, and so you cannot have a ? or a !.
test.two // nil
test.two
is a String?
, which can be nil. Because it is optional, you are allowed to return nil like you do in your code. The real value of this is .None
, and so the value you are seeing is actually a String? not a String.
test.two? // nil
This basically does the same thing as the one above, except you are explicitly saying that the value possibly is nil.
//test.two! // ERROR: EXEC_BAD_INSTRUCTION
You can never use a !
on a nil
value without expecting it to crash. When you use this operator, it forces a value out of it (so you would have a String, not String?). However, if the value is nil, there is no value to force out, so you end up crashing the program.
test.two == nil // true
test.two
as is clear returns nil, or .None (they are equivalent). And so if you compare nil == nil
or .None == .None
, it is true.
test.two? == nil // true
Same as the one above.
//test.two! == nil // ERROR: Cannot invoke == with an argument list of type (@lvalue String, NilLiteralConvertable)
Force-unwrapping a nil value crashes the program, every time. It doesn't make sense, either, because force-unwrapping would return a String
, not String?
. String
cannot be nil
.
//test.two.isEmpty // ERROR: String? does not have .isEmpty
Basically, whenever you want to call a method on an optional, you need to make sure it has a value using either optional-binding or optional-chaining (two separate things). String? is equal to Optional.Some(String), and you need to get past the optional layer to get to the string you want.
test.two?.isEmpty // nil
Here you use optional-chaining. Basically, the way this works is that test.two
is evaluated. If the value is .Some(String)
, then you call isEmpty
on the string. Otherwise, if it is .None
, then nothing happens. These optional chains can occur multiple lines per statement, such as test.two?.firstEmoji?
(assuming such a method were implemented.
//test.two!.isEmpty // ERROR: EXEC_BAD_INSTRUCTION
Again, force-unwrapping a nil optional is bad. Don't do it without first checking that the value is indeed .Some
.
test.three // nil
Since three
is implicitly unwrapped, and it was initialized to nil
(by not being set to something else), this shouldn't be surprising.
test.three? // nil
This is not something you are likely to use in real code since it's essentially optional chaining, but without anything after it. However here, since .three
is implicitly unwrapped ?
has the effect of "re-wrapping" it: the type of the result is now String?
. This make little difference here, but see what it does below, after test.three
has had a String
value assigned.
//test.three! // ERROR: EXEC_BAD_INSTRUCTION
As above is not possible to unwrap nil
. This may seem confusing since the declaration is often described as producing a variable that is "implicitly unwrapped"; but that should be read as "implicitly unwrapped if it is not nil
".
test.three == nil // true
test.three? == nil // true
//test.three! == nil // ERROR: Cannot invoke == with an argument list of type (@lvalue String, NilLiteralConvertable)
Same as 2 above. If you have a force-unwrapped variable, a ? appears to un-force-unwrap it, which is a behavior I would not advise. Try to use force-unwrapped optionals infrequently, mostly having to do with parts of the UI if you really need to. Often times, it will crash when you don't expect it to.
//test.three.isEmpty // ERROR: EXEC_BAD_INSTRUCTION
test.three?.isEmpty // nil
//test.three!.isEmpty // ERROR: EXEC_BAD_INSTRUCTION
When an optional is not assigned, it defaults to nil. If you then try to force-unwrap it... I think you get the idea. The first and third lines try to call a method from String on nil (works in ObjC, not Swift). Second one uses optional chaining to check if it is nil before calling the method, which it can't because it know it is nil.
test.two = "???" // {one "" {Some "???"} nil}
test.three = "!!!" // {one "" {Some "???"} three "!!!"}
This sets test.two
equal to .Some("???")
and test.three
equal to .Some("!!!")
The output you see simply shows all the variables held in the class, and how they change.
test.two // {Some "???"}
test.two? // {Some "???"}
test.two! // "???"
test.two
is now .Some("???")
, so when you call it, that is what is returned: a String? with a value. When you force-unwrap it, it now returns the value held in .Some
without crashing because there is indeed a String in it.
test.two == nil // false
test.two? == nil // false
test.two
is still an optional, so in the first two, when it compares them to nil, it realizes, "Hey, there is .Some value, so it is not nil."
//test.two! == nil // ERROR: Cannot invoke == with an argument list of type (@lvalue String, NilLiteralConvertable)
Force-unwrapping a value turns the value of test.two
from a String? to a String. Strings are never nil, because if they were, they would need to be optional. Comparing a value that is definitely a String to nil would make no sense, because you know for a fact that it is not nil; otherwise, the program would have crashed before!
//test.two.isEmpty // ERROR: String? does not have .isEmpty
test.two
is a String?, not a String. In order to access the string itself, you need to make sure it is there to access, using either a ? or a !
test.two?.isEmpty // {Some false}
This says, "If test.two
contains a String (not nil), then find if it is empty." It says {Some false}
because you are accessing a member of an Optional still, not a direct String.
test.two!.isEmpty // false
!, on the other hand, does return a String. Calling .isEmpty
on a String is either true
or false
, which in this case is false
because you set it equal to a non-empty string.
test.three // "!!!"
test.three
force-unwraps the String from it, which in this case works because it has a value.
test.three? // {Some "!!!"}
You treat this as a normal optional (not force-unwrapped), and so you get a Some(String) instead of just a String.
test.three! // "!!!"
Since you force-unwrapped it in its declaration, it is force-unwrapped here.
test.three == nil // false
This is another strange behavior, as it should probably be an error. It is supposed to be a String, which cannot be compared to nil, but something wacky is going on here. I will get back to you when I find out about this.
test.three? == nil // false
Treats test.three
as a normal optional, and checks if its state is .None
, or nil.
//test.three! == nil // ERROR: Cannot invoke == with an argument list of type (@lvalue String, NilLiteralConvertable)
What the one two above should be like. Can't compare a String to nil, so it throws an error.
test.three.isEmpty // false
Looks at the string value that was forced out (which exists; otherwise, it would have crashed). The string is not empty, so it is false.
test.three?.isEmpty // {Some false}
Treats it as a String?. If test.three
is not nil, then it takes the value from .Some (a String), and evaluates if it is empty, which it is not.
test.three!.isEmpty // false
The String is forced out of the optional, and isEmpty is called directly to it. It is not empty, so it returns false.
I hope I helped clarify things, and I will let you know why that one case is the way it is when I find out for myself :]