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.

+3


source to share


1 answer


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:]

+4


source







All Articles