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

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

将比以下更有用:

let someString: String = "this is the string"

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


考虑一个对象在构造和配置时可能具有nil属性,但在构造和配置后是不可变的且非nil的情况(NSImage通常是这样处理的,尽管在这种情况下,有时改变仍然是有用的)。隐式地打开可选选项可以很好地清理代码,而且安全性损失相对较低(只要有一个保证,它就是安全的)。

(编辑)需要明确的是:常规的可选选项几乎总是更可取的。


一行(或几行)简单的示例并不能很好地涵盖可选项的行为——是的,如果你声明一个变量并立即为它提供一个值,那么可选项就没有意义了。

到目前为止我见过的最好的情况是在对象初始化之后发生的设置,然后是“保证”在该设置之后的使用,例如在视图控制器中:

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不能识别这个保证(至少在Apple解决停止问题之前),所以你告诉编译器它存在。

不过,这在一定程度上破坏了类型安全。如果你的“保证”并不总是有效,那么任何你拥有隐式展开可选选项的地方都可能导致应用崩溃,所以这是一个需要谨慎使用的功能。此外,使用!总是让人觉得你在大喊大叫,没人喜欢这样。


隐式打开可选选项对于将属性表示为非可选属性非常有用,而实际上它需要是可选的。这通常是在两个相关对象(每个对象都需要对另一个对象的引用)之间“打结”所必需的。当两个引用实际上都不是可选的,但在初始化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。

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


隐式可选项的基本原理很容易解释,首先看一下强制展开的基本原理。

强制展开可选对象(隐式或非隐式),使用!操作符,意味着您确定您的代码没有错误,并且可选选项已经在它被打开的地方有一个值。没有!运算符,你可能只需要断言一个可选的绑定:

 if let value = optionalWhichTotallyHasAValue {
     println("\(value)")
 } else {
     assert(false)
 }

哪个不如

println("\(value!)")

现在,隐式可选项允许您表示拥有一个可选项,您希望在所有可能的流中,该可选项在展开时始终具有一个值。因此,它只是进一步帮助您-通过放松编写!每次打开,并确保运行时仍然会出错,以防您对流的假设是错误的。


隐式打开可选选项是一种实用的妥协,可以使混合环境中的工作与现有的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.

...在这里的另一边


在我描述隐式拆包可选项的用例之前,你应该已经理解了什么是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的变量当然不会造成太大伤害。


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属性声明为隐式展开的可选属性。


如果你确定从可选对象返回值而不是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…


隐式解包装可选(IUO)

它是Optional的一种语法糖,不会强制程序员打开变量。它可以用于在两阶段初始化过程中不能初始化的变量,并暗示非nil。这个变量本身表现为非nil,但实际上是一个可选变量。一个很好的例子是- Interface Builder的outlet

可选的通常更可取

var implicitlyUnwrappedOptional: String! //<- Implicitly Unwrapped Optional
var nonNil: String = ""
var optional: 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
}