加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 百科 > 正文

Learning Swift: Optional Types(详解Optional)

发布时间:2020-12-14 06:17:52 所属栏目:百科 来源:网络整理
导读:Note: this post is part of a series about the Swift programming language,introduced at WWDC 2014. I’m no more experienced in Swift than anyone else outside Apple,but I learn best by coding and talking through a problem. If there’s a bett

Note: this post is part of a series about the Swift programming language,introduced at WWDC 2014. I’m no more experienced in Swift than anyone else outside Apple,but I learn best by coding and talking through a problem. If there’s a better way to approach some of these topics,get in touchon Twitter!

We’ve had a little over two weeks to play with theSwift programming languagenow,and one sharp edge that keeps coming up is the language’s inclusion of what they call “optional types”. The vast majority of Objective-C developers are familiar with the use ofnilto indicate a nonexistent value,but communicating that kind of information through a variable’s type is a bit more foreign.

In this post,we’ll have an introductory discussion about how Swift provides optional types,go over a couple of implementation details,and point out a few tough spots in the optional system.

Types,Maybe?

Before we dive into code,let’s talk a bit about what it means for a type to be optional. A lot of the variables we’ll encounter will have a “regular,” non-optional type; these range from everyday value types (likeInt,Bool,orString) to more complex class types (such asUIView).

When we declare variables of these types,Swift requires thatthey always have a value. In fact,this requirement is so strict that attempting to use a variable before initializing it with a value is a compile error:

var x: Int
println(x) // Error: Variable 'x' used before being initialized

This may seem frustrating on the surface,but is actually incredibly helpful in the long run: by preventing this code from compiling,Swift is eliminating an entire class of potential runtime errors that would arise from using an uninitialized value. In some cases,this requirement is even more strict – if we were to useletinstead ofvar,we’d find ourselves facing an error on the declaration itself,rather than on the first call site:

let x: Int // Error: 'let' declarations require an initializer expression

Naturally,we can silence this error by providing a value forx:

let x = 42 println(x) // prints "42"

However,astute developers will notice that we can’t providexanilvalue at initialization time:

let x: Int = nil

This kind of code produces a rather opaque error about finding an overload for__conversion,but what it really means is simpler: we can’t provide anilvalue for variables of a non-optional type. Sincexis a plainIntvalue,whichnilis not.

This is where optional types come in. Swift lets you make virtually every type optional by appending a question mark to the type name. For example,we can shoehorn anilvalue into ourxfrom earlier just by tacking that?onto theInttype in the declaration:

var x: Int? = nil println(x) // prints "nil" x = 42 println(x) // prints "42"

At this point,we’ve come around to what most developers would expect in Objective-C: object variables can have a “real” value ornil,and it’s just up to you to check which case your code is handling.

Little Boxes

“But wait,” you say,“Int is avaluetype,not an object! How can I usenilfor a value? There was no such thing for NSInteger…”

Well,you’re right. NSInteger didn’t have anilvalue (or,rather,usingnilwith the right coercion would get you an integer with a value of 0). Instead,we defined a ton of marker values that meant “no value:”0,monospace; font-size:0.9em">1,monospace; font-size:0.9em">NSIntegerMin,monospace; font-size:0.9em">NSIntegerMax,andNSNotFoundall mean “nothing” in some API.

When you stop to think about it,this is really a limitation: by not having a consistent,defined way of saying “no integer value,” we’re layering a tiny bit of additional complexity around any use of such a value,then attempting to paper over it with documentation. Want to find an object in an array? Well,if that object doesn’t exist,you getNSNotFound– but if you try to find a nonexistent row in a table view,monospace; font-size:0.9em">-1instead.

Swift provides a cleaner way around this problem with the kind of optional types we describe above. How can it work with any type,though? Well,under the hood,an optional type is,well,just a type:

enum Optional<T> { case None case Some(T) }

The above is the actual definition ofOptionalin the Swift core library (slightly abridged for clarity). Swift defines anew typecalled Optional that always has exactly one of two values: a defined “nothing” value calledNone,or a wrapped-up value of some other typeT. It’s as if Swift can take regular values and place them inside a box,which may or may not be empty:

In this example,the first integer is a plainInttype. The second and third,though,are both of typeOptional<Int>– or,for short,monospace; font-size:0.9em">Int?. Notice that the third value here is actually an “empty box” (theNonevalue),even though its type isInt?.

This ability,to pass aroundNoneanywhere an optional type can go,is how Swift can provide things likenilfor value types likeInt(or,for that matter,anytype,whether value or reference). Since this value will have the same type as a “real” value wrapped up inOptional,they can both be represented in the same variable without trying to rely on special values to stand in for the concept of “no value.”

Unwrapping

This poses a problem of its own,though. Now that we know optionals are their own type,we realize that they can’t be passed anywhere their underlying type can:

func double(x: Int) -> Int { return 2 * x } let y: Int? = .Some(42) println(double(y)) // error: Value of optional type 'Int?' not unwrapped

We need some way of getting at the value inside an optional’s box – and,checking whether such a value exists at all! Thankfully,Swift has us covered with the!operator,which extracts the value out of an optional:

let y: Int? = .Some(42) println(double(y!)) // prints '84'

This works great for optionals that have a value. But what about those that don’t?

let y: Int? = nil // same as Optional.None println(double(y!)) // runtime error: Can't unwrap Optional.None

The!operator only applies to optionals that have an actual value inside them. If your optional hasnil(an alias for.None),it can’t be unwrapped and will throw a runtime error.

Let’s make our code a bit smarter. Instead of unconditionally unwrapping our optional value,we can check whether the value isnilfirst – much like we might have done in Objective-C.

let y: Int? = nil if y { println(double(y!)) } else { println("No value to double!") // prints "No value to double!" }

But what if we gotyfrom another method altogether,instead of defining it locally? It would feel a bit verbose to require a newletorvarstatement wherever we intend to call afunc,then immediately check that just-declared variable.

Swift has us covered here too,with a syntax calledoptional binding. By combining anifand aletstatement,we can write a concise one-line check for a newly-bound variable that is only conjured into existence if there’s a real value to go along with it:

if let y: Int? = someObject.someInt() { println(double(y)) } else { println("No value to double!") // prints "No value to double!" }

You may have noticed that here,we don’t even need to explicitly unboxyusing the!operator. This is another convenience we get for free by using optional binding: the bound variable is of the underlying type (in this caseInt),instead of keeping the wrappingOptionaltype. Since we’re sure such a value exists,we can access it directly inside the body of theifstatement,rather than having to unwrap it by hand.

Chaining

Now we’ve built up a concise syntax for checking and unwrapping optional variables. But what about calling methods on those variables? Your program might have some custom classes – most do,after all – and you could want to call a method on an variable that might be an instance,or might benil. In Objective-C,trying the latter would just give you anothernil-like value right back.

Thankfully,Swift anticipated this case too. Developers can make use ofoptional chainingto call methods on potentially-nilobjects:

let y: SomeClass? = nil let z = y?.someMethod() // will produce nil

By sticking a?between the variable name and method call,we can indicate that we want either a real answer back (in the event thatyis a valid instance) or anothernil(in the case thatyis itselfnil). This should feel very familiar to Objective-C pros: it’s exactly what that language would do in this situation.

The one caveat: the type of the returned value willalwaysbe optional,even if the method itself declares a non-optional return type. Since the value being computed could becomenilat any point along the chain (if the object being called isnil),the return value has to be prepared for that possibility,and the only type we have in Swift capable of carrying anilvalue is an optional. Consider:

class SomeClass { func someMethod() -> Int { return 42 } } let y: SomeClass? = nil let z = y?.someMethod() // of type Optional<Int> with value nil

Even thoughsomeMethod()is declared to return anzgets typeOptional<Int>because we used optional chaining to call the method.

This might seem like a hassle,but can actually be helpful,especially when combined with optional binding from above. If we stick with the same class definition,we can try something like this:

let y: SomeClass? = nil if let z = y?.someMethod() { println(double(z)) } else { println("Couldn't get value from someMethod()") }

This remains concise while still dealing with all the various concerns we might have:

  • Ifyisnil(as it is here),the optional chaining will still allow us to write this code without a type error
  • IfnilorsomeMethod()returnsnilvalue for non-optionalz
  • In the event we do get az,we’re not required to hand-unwrap it because it’s optionally bound

All in all,this is a pretty clean system for passing aroundnilvalues for just about any type. We get some extra type safety out of the deal,avoid using specially defined values,and can still be just as concise as Objective-C – if not more!

Rough Edges

That’s not to say the optional system isn’t without its quirks,though. A few rough edges in early versions of Swift can lead unwary developers into unexpected situations.

Unary?operator

It’s (currently) valid Swift to take an optional variable and throw a?at the end. However,unlike the unwrapping operator!,appending?doesn’t actually affect the variable in any way: it is still optional,and surroundingifchecks will still look to see if the variable is This can cause extra trouble when combined with…

Optional<Bool>

Since the Optional type is defined using generics – that is,it can wrap any other type in the language – it’s possible to construct an optional boolean variable. In fact,it’s virtually mandatory the language allow this: to special-caseBoolto disallow optionals would be an exceptional change,requiring serious modifications to the language or the Optional type.

That does,however,lead to a way developers can construct a kind of three-state variable: anOptional<Bool>can betrue,monospace; font-size:0.9em">false,monospace; font-size:0.9em">nil. (What the latter means is rather context-dependent.) This can be very misleading,when combined with anifcheck:

let x: Optional<Bool> = .Some(false) if x { println("true") } else { println("false") } // prints "true"

Since theifin this case checks whetherxisx!,this code snippet prints “true”. Even worse,it’s possible to write the same code with a tiny tweak:

let x: Optional<Bool> = .Some(false) if x? { println("true") } else { println("false") } // prints "true"

As discussed above,the unary?operator has no effect in this context – what looks like an unwrapped optional boolean is actually still optional,leading the snippet to continue printing “true.” (To really unwrapx,we need to use the!operator.)

Implicit implicit unwrapping

Interface Builder is an important component of most iOS and Mac apps,and as Xcode continues to develop new features,it will only grow more so. However,not everything is possible in IB; developers will often hook up IBOutlets to gain programmatic control over different UI elements.

In Swift,the annotation to expose a variable to IB is nearly identical to its Objective-C counterpart:

class MyViewController: UIViewController { @IBOutlet var myView: UIView }

This will exposemyViewto be hooked up in Interface Builder. At this point,what’s the type ofmyView?UIView,right?

Wrong. In Swift,marking a variable with@IBOutletimplicitly turns that variable into an implicitly unwrapped optional,even if its declared type doesn’t include the!annotation. What’s worse,letting Xcode create this variable declaration – perhaps by control-dragging from a .xib or storyboard – will write it as shown above,without the extra!after the type. To be extra-correct,we should instead write:

class MyViewController: UIViewController { @IBOutlet var myView: UIView! }

Note that this quirk is also virtually required: since IBOutlets don’t strictly need to be hooked up in a .xib or storyboard,it’s always possible that an outlet will wind up with anilvalue at runtime. Using implicitly unwrapped optionals allows for this case without mandating significant changes to existing codebases or requiring use of optional binding and chaining everywhere IB comes into play.

Necessary evils

Most of these problems wind up being required by the language’s core tenets,or by Objective-C compatibility,as described above. However,developers still need to balance the good with the bad when adopting Swift – and to keep in mind that the language is still in flux. Future changes may yet obviate several of these quirks;file radarsto encourage Apple to fix your favorite bug!


原文链接:http://lithium3141.com/blog/2014/06/19/learning-swift-optional-types/

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读