为什么要创build“隐式解包选项”?
为什么要创build一个“隐式解包可选”vs创build一个常规variables或常量。 如果你知道它可以成功解包,那么为什么要创build一个可选的第一个地方? 例如,为什么是这样的:
let someString : String! = "this is the string"
将会更有用vs:
let someString : String = "this is the string"
如果“optional indicate that a constant or variable is allowed to have 'no value'”
,但是“sometimes it is clear from a program's structure that an optional will always have a value after that value is first set”
,那么使其成为可选的首先? 如果你知道一个可选的总是会有一个值…不这样做…不是可选的?
考虑一个对象在构造和configuration时可能没有属性的情况,但之后是不可变的和非零的(NSImage通常以这种方式处理,尽pipe在这种情况下,它有时候还是有用的)。 隐含的解包select权将会很好地清理它的代码,相对较低的安全性损失(只要保证一个担保,这将是安全的)。
(编辑)但要清楚:常规选项几乎总是可取的。
在我可以描述隐式展开的Optionals的用例之前,你应该已经理解了Swift中Optionals和Implicitly Unwrapped Optionals的含义。 如果你不这样做,我build议你先阅读我关于期权的文章
何时使用隐式解包可选
有四个主要原因可以创build隐式解包可选。 所有这些都与定义一个永远不会被访问的variables,因为否则,Swift编译器将永远强迫你显式解开一个可选。
1.初始化期间不能定义的常量
每个成员常量在初始化完成时都必须有一个值。 有时候,一个常量在初始化过程中不能用正确的值初始化,但是在被访问之前仍然可以保证有一个值。
使用一个可选的variables可以解决这个问题,因为一个可选的自动初始化nil
,它最终将包含的值仍然是不可变的。 然而,不断地解开一个你知道肯定不是零的variables是一件痛苦的事情。 隐式解包选项与“可选”一样具有相同的优点,而且还有一个额外的好处,那就是无需在任何地方显式解开它。
一个很好的例子是,当一个成员variables不能在UIView子类中初始化,直到加载视图:
class MyView : UIView { @IBOutlet var button : UIButton! var buttonOriginalWidth : CGFloat! override func awakeFromNib() { self.buttonOriginalWidth = self.button.frame.size.width } }
在这里,你不能计算button的原始宽度,直到视图加载,但你知道awakeFromNib
将在视图上的任何其他方法(除初始化之外)之前被调用。 您可以将其声明为隐式解包的可选项,而不是强制将值显式解开为无形的整个类。
2.与Objective-C API进行交互
Objective-C中每个对象的引用都是一个指针,这意味着它可以是nil
。 这意味着,与Swift的Objective-C API的每一个交互必须使用一个可选的地方有一个对象的引用。 您可以在每种情况下都使用普通的Optional,但是如果确实知道引用不nil
,则可以通过将其声明为隐式解包可选来保存解包代码。
一个很好的例子是UITableViewDataSource
:
编辑: UITableViewDataSource示例不再有效。 苹果公司改进了API,没有任何参数是可选的,也不是返回值。
override func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell? { return nil }
在这里,你知道这个方法永远不会被调用没有tableView
或indexPath
。 检查它nil
是浪费时间。 如果这是一个纯粹的Swift API,则根本不会声明它是可选的。
3.当你的应用程序无法从一个variables无法恢复
这应该是非常罕见的,但是如果您的应用程序在string不能继续运行,如果一个variablesnil
时访问,这将是浪费时间打扰testingnil
。 通常情况下,如果您的应用程序必须完全正确,才能继续运行,则可以使用assert
。 一个隐式解包可选有一个内置的权利为零。
4. NSObject初始化器
苹果确实有至less一个奇怪的情况下隐式unwrapped选项。 从技术上讲,所有从NSObject
inheritance的类的初始化方法都返回Implicitly Unwrapped Optionals。 这是因为Objective-C中的初始化可以返回nil
。 这意味着,在某些情况下,您仍然希望能够testing初始化nil
的结果。 一个完美的例子是UIImage
如果图像不存在:
var image : UIImage? = UIImage(named: "NonExistentImage") if image != nil { println("image exists") } else { println("image does not exist") }
如果您认为图像不存在并且可以正常处理该场景,则可以将捕获初始化的variables声明为“可选”,以便您可以检查它nil
。 你也可以在这里使用Implicitly Unwrapped Optional,但是由于你打算检查它,所以最好使用普通的Optional。
何时不使用隐式解包可选
1.懒散的计算成员variables
有时你有一个不应该是零的成员variables,但是它在初始化期间不能被设置为正确的值。 一个解决scheme是使用隐式解包可选,但更好的方法是使用一个懒惰的variables:
class FileSystemItem { } class Directory : FileSystemItem { lazy var contents : [FileSystemItem] = { var loadedContents = [FileSystemItem]() // load contents and append to loadedContents return loadedContents }() }
现在,成员variables的contents
直到第一次被访问时才被初始化。 这使得class级有机会在计算初始值之前进入正确的状态。
注:这似乎与上面的#1相矛盾。 但是,还有一个重要的区别。 上面的buttonOriginalWidth
必须在viewDidLoad期间设置,以防止任何人在访问属性之前更改button宽度。
2.其他地方
大多数情况下,应该避免隐式展开的选项,因为如果使用错误,整个应用程序将在nil
访问时崩溃。 如果您不确定某个variables是否可以为零,则始终默认使用正常可选。 解开一个永远不会nil
的variables当然不会太痛苦。
隐式解包选项对于将属性呈现为非可选属性是非常有用的,因为它在实际需要时是可选的。 这往往是两个相关对象之间“捆绑”的必要条件,每个对象都需要引用另一个对象。 当这两个引用实际上都是可选的时,这是有意义的,但是当它们被初始化时,其中一个引用是零。
例如:
// These classes are buddies that never go anywhere without each other class B { var name : String weak var myBuddyA : A! init(name : String) { self.name = name } } class A { var name : String var myBuddyB : B init(name : String) { self.name = name myBuddyB = B(name:"\(name)'s buddy B") myBuddyB.myBuddyA = self } } var a = A(name:"Big A") println(a.myBuddyB.name) // prints "Big A's buddy B"
任何B
实例总是应该有一个有效的myBuddyA
引用,所以我们不想让用户把它当作可选的,但是我们需要它是可选的,以便在我们有一个A
引用之前我们可以构造一个B
然而! 这种相互参照的要求常常是紧密耦合和不良devise的performance。 如果你发现自己依赖于隐式的解包选项,你应该考虑重构来消除交叉依赖。
隐式解开的选项是务实的妥协,使混合环境中的工作必须与现有的Cocoa框架及其约定进行互操作,同时也允许逐步迁移到更安全的编程范例 – 无空指针 – 由Swift编译器执行。
Swift一书中,在“基础知识”一章中,“ 隐式解包选项 ” 一节说:
当一个可选的值被确认在第一次定义的可选值之后立即存在时,隐式解包的可选值是有用的,并且可以肯定地假定在随后的每个点都存在。 Swift中隐式展开的option的主要用途是在类的初始化过程中,如Unowned References和Unwinedly Unwrapped Optional Properties中所描述的 。
…
你可以想象一个隐式解包的可选方法,只要使用它就可以自动解包可选的赋予权限。 每次使用时,不要在可选名称后面放置感叹号,而是在声明它时在可选types后放置感叹号。
这归结为通过使用约定来确定非零属性的情况,并且在类初始化期间不能由编译器强制执行。 例如,从NIB或Storyboard初始化的UIViewController
属性(初始化分为不同的阶段),但在viewDidLoad()
之后,您可以假定属性通常存在。 否则,为了满足编译器的需要,你必须使用强制解包 , 可选绑定或可选的链接来掩盖代码的主要目的。
以上部分来自Swift的书也参考了自动引用计数章节 :
但是,还有第三种情况,其中两个属性应始终有一个值,一旦初始化完成,这两个属性都不应该
nil
。 在这种情况下,将一个类的非拥有属性与另一个类的隐式解包可选属性相结合是有用的。这样一旦初始化完成,这两个属性都可以直接访问(不包括可选的展开),同时还可以避免参考周期。
这归结为不是一个垃圾收集语言的怪癖,因此保留周期的打破作为一个程序员在你身上,隐含的解包选项是隐藏这个怪癖的工具。
这涵盖了“什么时候在你的代码中使用隐含的解包选项?”的问题。 作为应用程序开发人员,您大多会在Objective-C编写的库的方法签名中遇到它们,而这些方法签名不具备表示可选types的能力。
从Cocoa和Objective-C使用Swift开始, 使用nil :
因为Objective-C没有保证对象是非零的,所以Swift使得在导入的Objective-C API中所有的参数types和返回types都是可选的。 在使用Objective-C对象之前,应该检查以确保它不会丢失。
在某些情况下,您可能绝对确定Objective-C方法或属性永远不会返回
nil
对象引用。 为了使这个特殊场景中的对象更加方便,Swift将对象types导入为隐式展开的可选项 。 隐式解包可选types包括可选types的所有安全特性。 此外,您可以直接访问该值,而无需检查nil
或自己解包。 当您访问这种types的可选types的值时,如果不安全地解包它,隐式解包的可选操作将检查值是否丢失。 如果缺less该值,则会发生运行时错误。 因此,除非您确定该值不能丢失,否则应该始终检查并解包一个隐式解包的可选项。
…在这里以外
一行(或几行)简单的例子不能很好地涵盖可选项的行为 – 是的,如果你声明一个variables并立即提供一个值,那么在可选项中没有任何意义。
目前为止我见过的最好的情况是在对象初始化之后进行设置,然后使用“保证”来遵循该设置,例如在视图控制器中:
class MyViewController: UIViewController { var screenSize: CGSize? override func viewDidLoad { super.viewDidLoad() screenSize = view.frame.size } @IBAction printSize(sender: UIButton) { println("Screen size: \(screenSize!)") } }
我们知道printSize
会在视图加载后被调用 – 这是一个连接到视图内的控件的动作方法,我们确保不要调用它。 所以我们可以保存一些可选的检查/绑定!
。 Swift无法识别这个保证(至less在Apple解决暂停问题之前),所以你告诉编译器它存在。
虽然这在某种程度上打破了types安全。 任何你有一个隐含的解包可选的地方是你的应用程序可以崩溃,如果你的“保证”并不总是保持,所以这是一个function,谨慎使用。 另外,使用!
所有的时间听起来就像你在喊,没有人喜欢。
Apple在Swift编程语言 – > 自动引用计数 – > 解决类实例之间的强引用周期 – >无主引用和隐式解包可选属性中给出了一个很好的例子
class Country { let name: String var capitalCity: City! // Apple finally correct this line until 2.0 Prerelease (let -> var) init(name: String, capitalName: String) { self.name = name self.capitalCity = City(name: capitalName, country: self) } } class City { let name: String unowned let country: Country init(name: String, country: Country) { self.name = name self.country = country } }
City
的初始化程序从Country
的初始化程序中调用。 但是,Country
的初始化程序无法将self
传递给City
初始化程序,直到新的Country
实例完全初始化为止,如“ 两阶段初始化”中所述 。为了应付这个要求,你把
Country
的capitalCity
属性声明为隐式解包的可选属性。
隐含的select权的基本原理更容易解释,首先看强制解开的基本原理。
强制解包一个可选(隐含或不可),使用! 运算符,意味着你确定你的代码没有错误,并且可选项已经有一个值被解包的地方。 没有! 运算符,你可能会断言一个可选的绑定:
if let value = optionalWhichTotallyHasAValue { println("\(value)") } else { assert(false) }
这不如…
println("\(value!)")
现在,隐式选项让你expression一个可选的,你希望在所有可能的stream程中解包时总是有一个值。 所以它只是帮助你更进一步 – 通过放宽写作的要求! 每次打开包装,并确保运行时仍然错误,以防您对stream程的假设错误。
如果你确实知道,从可选的而不是nil
返回值, 隐式解包的选项用来直接从可选项中获取这些值,非选项则不能。
//Optional string with a value let optionalString: String? = "This is an optional String" //Declaration of an Implicitly Unwrapped Optional String let implicitlyUnwrappedOptionalString: String! //Declaration of a non Optional String let nonOptionalString: String //Here you can catch the value of an optional implicitlyUnwrappedOptionalString = optionalString //Here you can't catch the value of an optional and this will cause an error nonOptionalString = optionalString
所以这是使用的区别
let someString : String!
并 let someString : String