【问题标题】:Why to avoid forced unwrapping为什么要避免强制展开
【发布时间】:2017-10-12 17:27:36
【问题描述】:

在某些情况下您忘记设置值(因此它实际上是一个错误),并且在强制解包的情况下运行程序可能会导致问题崩溃,这可以让您找到忘记设置值的错误你应该设置的。

从谈论避免强制展开的帖子中,总是提到强制展开会使程序崩溃,因此这是一件坏事。当问题实际上有错误时崩溃有什么不好?

请举例说明强制展开可能不好。

(我并不是说强制解包适用于所有情况。)

【问题讨论】:

  • 在大多数情况下,这些答案都是关于生产软件的。在开发过程中,崩溃可能是获取有关错误信息的最有用的响应,但真实用户往往不喜欢意外消失的应用程序。此外,大多数具有这种答案的问题都是由使用强制展开但不了解导致崩溃的原因的人提出的,而不是由将其用作调试工具的人提出的。

标签: swift optional


【解决方案1】:

只有当你作为程序员知道一个可选项永远不会是nil,除非nil表示您的代码在开发过程中存在明显的错误(然后您希望它崩溃)。

有很多例子表明这种类型的强制展开是合适的。示例包括:

  • 获取应用程序包中已知文件的路径(nil 表示您在开发过程中忘记定位该文件)。
  • 强制调用UITableView dequeueReusableCell(nil 表示您的情节提要中有错误)。
  • 从 DateComponents 获取特定组件时,您刚刚专门向 Calendar 询问了该组件(nil 表示您有错字)。

显然还有许多其他情况适合强制展开,但您必须清楚地了解这些情况。

但也有同样多的运行时决策会导致您无法保证的可选项,因此不应强制解开此类情况。

示例包括:

  • 处理任何用户输入。永远不要假设用户输入了有效数据。永远不要假设一个值可以按预期转换。始终检查 nil 结果。
  • 解析 JSON 结果。永远不要假设您获得的数据与某种预期格式匹配,即使该格式已明确记录并且似乎总是有效。事情会随着时间而改变。优雅地处理此类意外数据,而不是仅仅假设某个值将始终存在并且属于假设的数据类型。
  • 处理任何可以throw 或返回可选结果的API。事情可能会出错。发生错误。永远不要假设你会得到一个有效的答案。防御性代码。

最后,具有适当经验并了解选项如何工作、它们的含义以及值何时可能或可能永远不是nil 的开发人员能够在适当的时候安全地使用强制展开。明智地使用它。

永远不要仅仅因为 Xcode 建议它让编译器满意就使用强制解包。

【讨论】:

  • 在您的示例中进行适当的强制解包,最好抛出错误告诉开发人员该文件尚未成为目标,或者情节提要中有错误,而不是等待用户在应用崩溃时发现此错误?
  • @Deco 如果用户在开发人员之前看到崩溃,并且崩溃是因为目标错误的文件或情节提要中的错误等公然的事情,那么开发人员甚至没有运行自己的应用程序,然后才给出它给用户。不要成为那个开发者。
  • 好点。我正在考虑更多的例子,当开发人员运行它时,应用程序不会 100% 崩溃,尽管我猜这不是使用强制展开标准的一部分。
  • 喜欢这句话“永远不要仅仅因为 Xcode 建议它让编译器开心就使用强制解包。”
【解决方案2】:

强制展开是不好的,因为不能保证您的程序在执行时访问实际变量。发生这种情况时,您的程序可能会尝试对不存在的数字执行数学计算,并且您的应用程序会崩溃。您在开发阶段的观点是,如果它崩溃了,您将能够缩小崩溃发生的原因并解决它在运行时为零的问题在您的开发阶段,但是在生产中呢?

例如,如果您要从 Web 服务中检索某种数字,您可能希望将此数字与本地数字(可能是版本号)进行比较:

if let json = try? JSONSerialization.jsonObject(with: responseData, options: .allowFragments) as? [String: Any], 
   let serverAPIVersion:NSNumber = json["API_Version_Number"] as? NSNumber {

    if self.currentAPIVersion.uintValue < serverAPIVersion.uintValue {
       self.updateVersionWith(number: serverAPIVersion)
    }

 }

在上面的代码中,我们从服务器获取的 JSON 中安全地解包 "API_Version_Number"。我们可以安全地展开包装,因为如果没有 "API_Version_Number" 的值,那么当我们尝试与当前版本进行比较时,程序就会崩溃。

// This will crash if the server does not include "API_Version_Number in json response data
let serverAPIVersion:NSNumber = json["API_Version_Number"] as! NSNumber

在生产中,有些事情是您无法控制的(很多时候是服务器端问题),可能会导致未填充的变量。这就是为什么最好有条件地展开以安全地访问代码中的值,以防止事情在执行时崩溃。

【讨论】:

    【解决方案3】:

    如果程序确实有错误,那么崩溃并没有什么问题......如果您无法解决崩溃问题

    但正如@phillip-mills 在他的评论中所说:

    ...在不了解导致崩溃的原因的情况下使用强制展开的人询问

    我们经常看到一些示例,人们出于某种意想不到的原因强制打开不存在的可选项,在这种情况下,尝试“优雅地”打开可选项是有意义的。

    错误的强制展开示例

    假设您有一个后端服务,它为您提供一些 JSON,您可以将其解析为模型对象并呈现在视图中。

    假设您有一个像这样的模型:

    struct Person {
        let firstName: String?
        let lastName: String?
    }
    

    UITableView 中,您使用人员对象填充一些单元格。大致如下:

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        //dequeue your cell
        //get your person element from some array
        //populate
        cell.firstNameLabel.text = person.firstName!
        cell.lastNameLabel.text = person.lastName! 
    
        return cell
    }
    

    在某些时候,您可能最终会得到 Person 而没有 firstName (您可以说这是后端的问题等等等等,但是......它可能会发生: ) ) 如果您强制展开,您的应用程序将在不需要的地方崩溃。在这种情况下,你本可以优雅地展开

    if let firstName = person.firstName {
        cell.firstNameLabel.text = firstName
    }
    

    或者你可以使用 Nil Coalescing Operator

    let firstName = person.firstName ?? ""
    

    最后,正如@rmaddy 在他的回答中所说的那样

    强制解包很糟糕,因为不能保证您的程序在执行时访问实际变量

    您无法始终确定数据是否符合您的预期。

    当你有可能只在你绝对确定你正在操作的数据是有效的情况下才进行一些操作时,也就是说,如果你已经安全地打开它......那就太愚蠢了不要使用该选项:)

    希望对你有所帮助。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-06-25
      • 1970-01-01
      • 2018-11-13
      • 2011-11-15
      • 1970-01-01
      • 2010-12-28
      相关资源
      最近更新 更多