Rosetta Stone for Additional Swift Types?
I understand (I think) about the basics of complementary types in Swift and roughly understand the difference between ?
and !
, but I am still puzzled about some of the results I get when I use these functions - in particular the role Some <T>
and how it differs from <T>
; some specific error messages that I get in certain cases; and how Some <T>
it seems to appear when I expect <T>
.
But I also feel that even when I understand individual cases, my understanding of the picture leaves me and I feel like there is some code here that I could decipher if I fully understood one simple example - Rosetta Stone if you want - for !
, ?
, additional values and unpacking.
For example, here is a simple and (I think) comprehensive catalog of main 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 can annotate this explaining what's going on with it in each case, I think this answer could serve as a solid reference to how these Swift features work.
source to share
Ok, I'll try to answer all of this. I may not have the connection to go through everything:
NOTE. Feel free to challenge me on errors. It took a while, so I certainly did a few.
Quick note: Optional
it is actually an enumeration. It has two states: .None
and .Some(T)
, where T
is the value type (in your case String
).
test.one
Foo
has a named property one
that returns String
. Specific String
, not optional, which means it will definitely make a difference. You feel the same way about how you just write "HI!"
in your code. The meaning of this is really""
//test.one? // ERROR: ? requires optional type
This is an error because test.one
, as said above, it returns a definite one String
, and therefore there is no way for it to be zero. You can guarantee that the return value exists.
//test.one! // ERROR: ! requires optional type
The same as?.! is a forced unwrapping operator, which means there is a possibility that test.one could be null, but you are forcing the value anyway (or fail if it isn't there). However, there is no chance that it is zero, and so you can't have it? or a !.
test.two // nil
test.two
is String?
, which can be nil. Since this is optional, you are allowed to return zero as in the code. The real value of this parameter .None
, and therefore the value you see is actually a string? not a string.
test.two? // nil
This basically does the same thing as above, except that you explicitly indicate that the value is possibly nil.
//test.two! // ERROR: EXEC_BAD_INSTRUCTION
You can never use !
by value nil
without expecting it to fail. When you use this operator, it outputs a value from it (so you have a string, not a string?). However, if the value is zero, the value is not preempted, so you end up crashing the program.
test.two == nil // true
test.two
as clear returns nil or .None (they are equivalent). So, if you are comparing nil == nil
or .None == .None
, it is true.
test.two? == nil // true
Same as above.
//test.two! == nil // ERROR: Cannot invoke == with an argument list of type (@lvalue String, NilLiteralConvertable)
Forcing the expansion to nil throws an error every time. That doesn't make sense either, because forced deployment will return a String
, not String?
. String
can't 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 matters by using optional chaining or optional chaining (two separate things). Line? equals to Optional. Some (String) and you need to go past an extra layer to get the string you want.
test.two?.isEmpty // nil
You are using optional chaining here. Basically, the way it works is with what is being evaluated test.two
. If the value is .Some(String)
, you call isEmpty
in a string. Otherwise, if it is .None
, nothing happens. These optional chains can have multiple lines for each statement, for example test.two?.firstEmoji?
(assuming such a method has been implemented.
//test.two!.isEmpty // ERROR: EXEC_BAD_INSTRUCTION
Again, forced unwrapping of a null parameter is bad. Don't do this without first checking if the value is valid .Some
.
test.three // nil
Since it is three
implicitly unwrapped and it was initialized nil
(without being set to anything else), this shouldn't be surprising.
test.three? // nil
This is not something you are likely to use in real code, as it is essentially not necessarily chained, but nothing after it. However, here, since it is .three
implicitly ?
unwrapped, has the effect of "re-wrapping" it: the result type is now String?
. It's a little different here, but see what it does below after the test.three
value is assigned String
.
//test.three! // ERROR: EXEC_BAD_INSTRUCTION
As above, it is impossible to expand nil
. This can seem confusing, since a declaration is often described as creating a variable that is "implicitly expanded"; but this should be read as "implicitly expanded 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 above. If you have a force unfolded variable, a? seems to untie it, which is behavior that I would not advise. Try using the optional force shutdown options, mostly with parts of the user interface, if you really need to. Often times, it will crash when you don't expect it.
//test.three.isEmpty // ERROR: EXEC_BAD_INSTRUCTION
test.three?.isEmpty // nil
//test.three!.isEmpty // ERROR: EXEC_BAD_INSTRUCTION
If the option is not assigned, it defaults to zero. If you then try to expand it ... I think you get the idea. The first and third lines try to call a method from String to nil (works in ObjC, not Swift). The second uses the optional chaining to test if it is nil before calling the method, which it cannot because it knows it is zero.
test.two = "???" // {one "" {Some "???"} nil}
test.three = "!!!" // {one "" {Some "???"} three "!!!"}
This sets test.two
equal .Some("???")
and test.three
equal .Some("!!!")
The output you see just shows all the variables contained in the class and how they change.
test.two // {Some "???"}
test.two? // {Some "???"}
test.two! // "???"
test.two
now .Some("???")
, so when you call it, this is what is returned: a string? with a value. When you force expand it, it now returns the value contained in .Some
without crashing because it actually has a String in it.
test.two == nil // false
test.two? == nil // false
test.two
is still optional, so in the first two cases, when it compares them to nil, it understands: "Hey there. Some value, so it's not zero."
//test.two! == nil // ERROR: Cannot invoke == with an argument list of type (@lvalue String, NilLiteralConvertable)
Does forced expansion of a value turn the value test.two
from a string? to the line. The strings are never null, because if they were they would be optional. Comparing a value that is definitely a String to nil doesn't make any sense, because you know it's not zero; otherwise the program would have crashed earlier!
//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 accessible using either? or a!
test.two?.isEmpty // {Some false}
It says, "If test.two
contains a string (not zero), then find if it is empty." He says {Some false}
because you are still accessing the Option element, not a straight String.
test.two!.isEmpty // false
!, on the other hand, returns a string. The call .isEmpty
to String is either true
or false
, which in this case is false
, because you are setting it equal to a non-empty string.
test.three // "!!!"
test.three
forces the String to detach from it, which in this case works because it matters.
test.three? // {Some "!!!"}
You treat this as a normal optional (not forced unwrapping) and so you end up with a Some (String), not just a string.
test.three! // "!!!"
Since you forcefully expand it in your ad, it is forcefully expanded here.
test.three == nil // false
This is different weird behavior as it is probably a bug. It is supposed to be a String which cannot match zero, but something strange is happening here. I will come back to you when I hear about it.
test.three? == nil // false
Treats test.three
as a normal optional parameter and checks if its state is .None
nil.
//test.three! == nil // ERROR: Cannot invoke == with an argument list of type (@lvalue String, NilLiteralConvertable)
Which should be one of the two above. There is no way to compare String to nil, so it throws an error.
test.three.isEmpty // false
Will look at the string value that was preempted (which exists, otherwise it would be broken). The string is not empty, so it is incorrect.
test.three?.isEmpty // {Some false}
Treats it as a string ?. If test.three
not nil, then it takes the value from .Some (String) and evaluates if it is empty, which it is not.
test.three!.isEmpty // false
The string is preempted from the option, and isEmpty is called directly on it. It is not empty, so it returns false.
I hope I helped clarify the situation and I will let you know why this is one case where I find out for myself:]
source to share