Answers:
在描述隐式展开的可选的用例之前,您应该已经了解Swift中的Optional和隐式展开的可选。如果您不这样做,建议您先阅读我的关于可选产品的文章
创建一个隐式展开的Optional的主要原因有两个。所有这些都与定义一个永远不会被访问的变量有关,nil
因为否则,Swift编译器将始终迫使您显式解开Optional。
初始化完成时,每个成员常量必须具有一个值。有时,常量在初始化期间无法使用其正确的值进行初始化,但仍可以保证在访问之前具有一个值。
使用Optional变量可以解决此问题,因为Optional将自动初始化,nil
并且它最终将包含的值仍然是不可变的。但是,不断地展开一个肯定不会为零的变量可能会很痛苦。隐式展开的Optional具有与Optional相同的优点,并且具有不必在任何地方显式展开它的附加优点。
一个很好的例子是,在加载视图之前,无法在UIView子类中初始化成员变量:
class MyView: UIView {
@IBOutlet var button: UIButton!
var buttonOriginalWidth: CGFloat!
override func awakeFromNib() {
self.buttonOriginalWidth = self.button.frame.size.width
}
}
在这里,在视图加载之前,您无法计算按钮的原始宽度,但是您知道 awakeFromNib
将在视图上的任何其他方法(初始化除外)之前调用。您可以将其声明为Implicitly Unwrapped Optional,而不是强制在整个类中无意义地显式地解开该值。
nil
这应该非常少见,但是如果nil
在访问变量时您的应用无法继续运行,则浪费时间进行测试nil
。通常,如果您的条件必须完全正确才能使应用程序继续运行,则可以使用assert
。隐式展开的Optional内置了一个nil的断言。即使这样,通常也可以拆开可选选项,并使用更具描述性的断言(如果为零)。
有时,您拥有一个成员变量,该变量永远不应为nil,但在初始化期间无法将其设置为正确的值。一种解决方案是使用隐式展开的Optional,但更好的方法是使用惰性变量:
class FileSystemItem {
}
class Directory : FileSystemItem {
lazy var contents : [FileSystemItem] = {
var loadedContents = [FileSystemItem]()
// load contents and append to loadedContents
return loadedContents
}()
}
现在,成员变量contents
直到第一次被访问才被初始化。这使班级有机会在计算初始值之前进入正确的状态。
注意:这似乎与上面的#1相矛盾。但是,有一个重要的区别。在buttonOriginalWidth
上述必须viewDidLoad中期间被设置为防止任何人改变属性被访问之前的按钮的宽度。
在大多数情况下,应避免使用“隐式解包的Optionals”,因为如果使用不当,当您访问时,您的整个应用程序将崩溃nil
。如果您不确定某个变量是否可以为nil,请始终默认使用常规的Optional。解开一个永远nil
不会带来不确定性的变量不会带来太大的伤害。
hasValue
是在Optional上定义的。我喜欢的语义hasValue
,以那些!= nil
。对于没有使用nil
其他语言的新程序员,我觉得这更容易理解。hasValue
比nil
。更具逻辑性。
hasValue
从beta 6中撤出的。Ash
隐式解开的可选选项对于在属性实际上需要在幕后为可选属性时将其表示为非可选属性很有用。这对于在每个需要相互引用的两个相关对象之间“打结”通常是必需的。当两个引用实际上都不是可选的,但是在初始化该对时,其中一个需要为零时,这是有意义的。
例如:
// 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
引用,因此我们不想让用户将其视为可选,但是我们需要将其视为可选,以便我们可以构造一个B
在A
引用之前。
然而!这种相互参考的要求通常表示紧密耦合和不良设计。如果您发现自己依赖于隐式解包的可选组件,则可能应该考虑进行重构以消除交叉依赖关系。
@IBOutlet
weak
声明并删除“无需创建强大的保留周期”即可
隐式展开的可选选项是实用的折衷方案,使在必须与现有Cocoa框架及其约定互操作的混合环境中的工作更加愉快,同时还允许逐步移植到Swift编译器强制执行的更安全的编程范例(无空指针)中。
一本Swift书,在“基础知识”一章的“ 隐式解开可选内容 ” 部分中说:
当在第一个定义了可选对象的值之后立即确认该可选对象的值存在并且可以肯定地假定此后的每个点都存在时,隐式解开的可选对象很有用。Swift中隐式解包的可选对象的主要用途是在类初始化期间,如无主引用和隐式解开的可选属性中所述。
…
您可以将隐式解压缩的可选内容视为允许使用该可选内容以使其自动解包的权限。不必在每次使用可选名称时都在其名称后放置一个感叹号,而是在声明它时在其可选类型后放置一个感叹号。
这归结于使用在案件非nil
-ness通过使用惯例确定性质的,并且不能由编译器类的初始化过程中强制执行。例如,UIViewController
从NIB或情节提要板初始化的属性,其中将初始化分为几个阶段,但是之后viewDidLoad()
您可以假定属性通常存在。否则,为了使编译器满意,您必须仅使用
强制解包,
可选绑定
或可选链接
来掩盖代码的主要用途。
Swift书的上方部分还涉及“ 自动引用计数”一章:
但是,存在第三种情况,在这两种情况下,两个属性都应始终具有值,并且
nil
初始化完成后,这两个属性都不应具有。在这种情况下,将一个类上的未拥有属性与另一类上的隐式展开的可选属性结合起来很有用。这样,初始化完成后就可以直接访问这两个属性(没有可选的展开),同时仍然避免了引用周期。
这归结为不是垃圾收集语言的怪癖,因此,作为程序员,保留周期的中断就在您身旁,而隐式展开的可选变量则是隐藏此怪癖的工具。
涵盖了“何时在代码中使用隐式解开的可选内容?” 题。作为应用程序开发人员,您经常会在用Objective-C编写的库的方法签名中遇到这些签名,而Objective-C无法表达可选类型。
从结合使用Swift和Cocoa和Objective-C中,使用nil部分:
因为Objective-C不能保证对象不是nil,所以Swift使所有类型的参数类型和返回类型在导入的Objective-C API中都是可选的。在使用Objective-C对象之前,应检查以确保没有丢失它。
在某些情况下,您可以绝对确定Objective-C方法或属性从不返回
nil
对象引用。为了使在这种特殊情况下的对象更易于使用,Swift将对象类型作为隐式解包的optional导入。隐式展开的可选类型包括可选类型的所有安全功能。此外,您无需检查是否可以直接访问该值nil
会将或自己解。当您在不首先安全地对其进行拆包的情况下访问这种可选类型的值时,隐式解包的可选检查该值是否丢失。如果缺少该值,则会发生运行时错误。因此,除非您确定不会丢失该值,否则应始终自己检查并解开一个隐式解包的可选文件。
...然后躺在这里
单行(或多行)的简单示例不能很好地说明可选变量的行为-是的,如果您声明一个变量并立即为其提供值,那么可选变量毫无意义。
到目前为止,我所见过的最好的情况是在对象初始化之后进行设置,然后“保证”使用它来遵循该设置,例如在视图控制器中:
class MyViewController: UIViewController {
var screenSize: CGSize?
override func viewDidLoad {
super.viewDidLoad()
screenSize = view.frame.size
}
@IBAction printSize(sender: UIButton) {
println("Screen size: \(screenSize!)")
}
}
我们知道printSize
在视图加载后将被调用—这是一个与该视图内的控件关联的action方法,我们确保不要以其他方式调用它。因此,我们可以使用来保存一些可选的检查/绑定!
。Swift无法识别这种保证(至少直到苹果公司解决了暂停问题),所以您告诉编译器它已经存在。
但是,这在某种程度上破坏了类型安全性。如果您的“担保”并不总是成立,则在任何具有隐式解包的可选内容的地方,应用程序都可能崩溃,因此该功能可少量使用。此外,一直使用!
时间听起来好像在大吼大叫,没人喜欢。
CGSizeZero
在实际使用中,大小可能是一个很好的标记值。但是,如果从笔尖加载的大小实际上可能为零,该怎么办?然后,CGSizeZero
用作哨兵并不能帮助您区分未设置的值和设置为零的值。此外,这同样适用很好地(后或其他地方从笔尖加载其他类型init
):字符串,以子视图的引用,等等
let foo? = 42
,而是let foo! = 42
)。这没有解决。(请注意,这可能是有关可选内容的相关答案,但与涉及不同/相关动物的隐式展开的可选内容无关。)
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
为了满足此要求,您可以将的
capitalCity
属性声明Country
为隐式展开的可选属性。
隐式可选的基本原理更容易通过首先查看强制展开的基本原理来解释。
使用!强制展开可选(隐式或非隐式)的包装 运算符,意味着您可以确定您的代码没有错误,并且可选代码在解包时已经具有一个值。没有!运算符,您可能只需声明一个可选绑定即可:
if let value = optionalWhichTotallyHasAValue {
println("\(value)")
} else {
assert(false)
}
不如
println("\(value!)")
现在,隐式可选选项使您可以表示具有一个可选选项,该选项在展开时在所有可能的流中都希望始终具有一个值。因此,通过放宽编写!的要求,它为您提供了进一步的帮助!每次解包,并确保在您对流的假设错误的情况下,运行时仍会出错。
init
其中设置出口变量(您甚至可能不会实现),但是它们重新保证后,设置awakeFromNib
/ viewDidLoad
。
如果您确定知道的话,值是从可选值返回的,而不是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
我认为 Optional
对于这种结构,是一个不好的名字,它使很多初学者感到困惑。
其他语言(例如Kotlin和C#)使用该术语 Nullable
,这使它的理解变得更加容易。
Nullable
表示您可以为该类型的变量分配空值。因此,如果是Nullable<SomeClassType>
,则可以为其分配空值(如果只是)SomeClassType
,则不能。这就是Swift的工作方式。
为什么要使用它们?好吧,有时候您需要使用null,这就是为什么。例如,当您知道要在一个类中具有一个字段,但是在创建该类的实例时不能将其分配给任何对象,但是稍后再说。我不会给出示例,因为人们已经在此处提供了示例。我只是写这个给我2美分。
顺便说一句,我建议您看看这在其他语言(例如Kotlin和C#)中如何工作。
这是解释Kotlin中此功能的链接: https //kotlinlang.org/docs/reference/null-safety.html
其他语言(例如Java和Scala)也有Optional
,但它们的工作方式与Optional
Swift中的s的,因为Java和Scala的类型默认情况下都是可为空的。
总而言之,我认为该功能应该Nullable
在Swift中命名,而不是Optional
...
Implicitly Unwrapped Optional
是一种语法糖,Optional
因为它不会迫使程序员拆开变量。它可以用于在初始化期间不能初始化的变量,two-phase initialization process
并且表示非零。此变量本身表现为非nil,但实际上是可选变量。一个很好的例子是-Interface Builder的插座
Optional
通常比较可取
var nonNil: String = ""
var optional: String?
var implicitlyUnwrappedOptional: String!
func foo() {
//get a value
nonNil.count
optional?.count
//Danderour - makes a force unwrapping which can throw a runtime error
implicitlyUnwrappedOptional.count
//assign to nil
// nonNil = nil //Compile error - 'nil' cannot be assigned to type 'String'
optional = nil
implicitlyUnwrappedOptional = nil
}
if someOptional
。