为什么要创建一个“隐式未包装的可选”vs只创建一个常规变量或常量? 如果您知道它可以成功地展开,那么为什么要首先创建一个可选选项呢? 举个例子,为什么是这样:

let someString: String! = "this is the string"

将比以下更有用:

let someString: String = "this is the string"

如果“可选项表示一个常量或变量允许‘没有值’”,但是“有时从程序的结构中可以清楚地看出,一个可选项在第一次设置值之后总是有一个值”,那么首先使它成为可选项的意义是什么? 如果你知道一个optional总是有一个值,那它不是可选的吗?


当前回答

如果你确定从可选对象返回值而不是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:字符串!然后让someString: String

其他回答

我认为Optional是这个结构的一个不好的名字,它会让很多初学者感到困惑。

其他语言(例如Kotlin和c#)使用术语Nullable,这使它更容易理解。

Nullable意味着可以将空值分配给这种类型的变量。如果它是Nullable<SomeClassType>,你可以给它赋空值,如果它只是SomeClassType,你不能。这就是Swift的工作方式。

为什么要使用它们?有时候你需要空值,这就是原因。例如,当您知道希望在类中有一个字段时,但是在创建该类的实例时不能将其分配给任何东西,但稍后会这样做。我就不举例子了,因为人们已经在这里提供了例子。我写这些只是为了表达我的意见。

顺便说一句,我建议你看看这在其他语言中是如何工作的,比如Kotlin和c#。

下面是Kotlin中解释这一特性的链接: https://kotlinlang.org/docs/reference/null-safety.html

其他语言,如Java和Scala确实有可选选项,但它们的工作方式与Swift中的可选选项不同,因为Java和Scala的类型默认都是可空的。

总而言之,我认为这个功能应该在Swift中被命名为Nullable,而不是Optional…

如果你确定从可选对象返回值而不是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:字符串!然后让someString: String

在我描述隐式拆包可选项的用例之前,你应该已经理解了什么是Swift中的可选项和隐式拆包可选项。如果你不知道,我建议你先阅读我关于可选选项的文章

何时使用隐式展开可选

创建隐式解包装可选对象有两个主要原因。所有这些都与定义一个在nil时永远不会被访问的变量有关,否则,Swift编译器总是会强制你显式地打开一个Optional。

1. 初始化时不能定义的常量

在初始化完成之前,每个成员常量都必须有一个值。有时,一个常量在初始化过程中不能用正确的值初始化,但在被访问之前仍然可以保证它有一个值。

使用可选变量可以解决这个问题,因为可选变量会自动初始化为nil,它最终包含的值仍然是不可变的。然而,不断地展开一个确定不是nil的变量可能是一件痛苦的事情。隐式打开可选选项实现了与可选选项相同的好处,额外的好处是不需要在所有地方显式打开它。

一个很好的例子是,一个成员变量不能在UIView子类中初始化,直到视图被加载:

class MyView: UIView {
    @IBOutlet var button: UIButton!
    var buttonOriginalWidth: CGFloat!

    override func awakeFromNib() {
        self.buttonOriginalWidth = self.button.frame.size.width
    }
}

这里,在视图加载之前不能计算按钮的原始宽度,但是您知道awakeFromNib将在视图上的任何其他方法(除了初始化)之前被调用。与其在整个类中强制显式地展开该值,不如将其声明为隐式未包装可选值。

2. 当你的应用程序不能从一个变量为nil恢复

这应该是非常罕见的,但如果你的应用程序不能继续运行,如果一个变量是nil访问时,这将是浪费时间的麻烦测试它为nil。通常情况下,如果你有一个条件必须为真,你的应用程序才能继续运行,你会使用断言。隐式解包装可选对象有一个内置于nil的断言。即使这样,如果可选对象为nil,最好还是展开可选对象,并使用更具描述性的断言。

何时不使用隐式解包装可选参数

1. 惰性计算的成员变量

有时你有一个成员变量不应该为nil,但在初始化时不能将它设置为正确的值。一种解决方案是使用隐式Unwrapped Optional,但更好的方法是使用惰性变量:

class FileSystemItem {
}

class Directory : FileSystemItem {
    lazy var contents : [FileSystemItem] = {
        var loadedContents = [FileSystemItem]()
        // load contents and append to loadedContents
        return loadedContents
    }()
}

现在,直到第一次访问成员变量时才初始化成员变量内容。这使类有机会在计算初始值之前进入正确的状态。

注意:这似乎与上面的第一条相矛盾。然而,这里有一个重要的区别。上面的buttonOriginalWidth必须在viewDidLoad期间设置,以防止任何人在访问属性之前更改按钮宽度。

2. 其他地方

在大多数情况下,隐式打开可选选项应该避免,因为如果错误地使用,你的整个应用程序将在nil时被访问时崩溃。如果你不确定一个变量是否可以为nil,总是默认使用普通的Optional。解开一个从不为nil的变量当然不会造成太大伤害。

隐式打开可选选项是一种实用的妥协,可以使混合环境中的工作与现有的Cocoa框架及其约定进行互操作更加愉快,同时还允许逐步迁移到更安全的编程范式——没有空指针——由Swift编译器强制执行。

Swift的书,在基础章节,部分隐式打开可选项说:

Implicitly unwrapped optionals are useful when an optional’s value is confirmed to exist immediately after the optional is first defined and can definitely be assumed to exist at every point thereafter. The primary use of implicitly unwrapped optionals in Swift is during class initialization, as described in Unowned References and Implicitly Unwrapped Optional Properties. … You can think of an implicitly unwrapped optional as giving permission for the optional to be unwrapped automatically whenever it is used. Rather than placing an exclamation mark after the optional’s name each time you use it, you place an exclamation mark after the optional’s type when you declare it.

This comes down to use cases where the non-nil-ness of properties is established via usage convention, and can not be enforced by compiler during the class initialization. For example, the UIViewController properties that are initialized from NIBs or Storyboards, where the initialization is split into separate phases, but after the viewDidLoad() you can assume that properties generally exist. Otherwise, in order to satisfy the compiler, you had to be using the forced unwrapping, optional binding or optional chaining only to obscure the main purpose of the code.

Swift书中的上述部分也参考了自动引用计数章节:

然而,还有第三种情况,在这种情况下,两个属性都应该有一个值,并且在初始化完成后,两个属性都不应该为nil。在这种情况下,将一个类上的无主属性与另一个类上隐式展开的可选属性结合起来是很有用的。 这使得在初始化完成后可以直接访问这两个属性(不需要可选的展开),同时仍然避免了引用循环。

这归结于不是垃圾收集语言的怪癖,因此保留周期的破坏是由程序员来承担的,隐式地打开可选选项是隐藏这个怪癖的工具。

这涵盖了“何时在代码中使用隐式展开的可选选项?””的问题。作为应用程序开发人员,您将在用Objective-C编写的库的方法签名中遇到它们,而Objective-C没有表达可选类型的能力。

在Cocoa和Objective-C中使用Swift,部分使用nil:

Because Objective-C does not make any guarantees that an object is non-nil, Swift makes all classes in argument types and return types optional in imported Objective-C APIs. Before you use an Objective-C object, you should check to ensure that it is not missing. In some cases, you might be absolutely certain that an Objective-C method or property never returns a nil object reference. To make objects in this special scenario more convenient to work with, Swift imports object types as implicitly unwrapped optionals. Implicitly unwrapped optional types include all of the safety features of optional types. In addition, you can access the value directly without checking for nil or unwrapping it yourself. When you access the value in this kind of optional type without safely unwrapping it first, the implicitly unwrapped optional checks whether the value is missing. If the value is missing, a runtime error occurs. As a result, you should always check and unwrap an implicitly unwrapped optional yourself, unless you are sure that the value cannot be missing.

...在这里的另一边

隐式打开可选选项对于将属性表示为非可选属性非常有用,而实际上它需要是可选的。这通常是在两个相关对象(每个对象都需要对另一个对象的引用)之间“打结”所必需的。当两个引用实际上都不是可选的,但在初始化pair时,其中一个引用需要为nil时,这是有意义的。

例如:

// 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。

然而!这种相互引用需求通常是紧耦合和糟糕设计的标志。如果您发现自己依赖于隐式展开的可选项,那么您可能应该考虑重构以消除交叉依赖。