这个Swift代码是等效的吗?

时间:2017-01-19 03:59:14

标签: ios swift xcode optional

我使用以下代码填充UITableView的单元格:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "Cell")! as UITableViewCell
    var someString:String
    cell.textLabel?.text = someString
    return cell
}

再玩一些,我发现我可以使用稍微不同的代码完成相同的任务:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as UITableViewCell!
    var someString:String
    cell?.textLabel?.text = someString
    return cell!
}

Xcode编译并执行两段代码就好了。作为一个Swift新手,它很难吸收选项,这种例子让我更加困惑。有人能解释一下这里发生了什么吗?代码是否相同?并且一种可选的风格比另一种更令人满意吗?

3 个答案:

答案 0 :(得分:4)

它们相似,但它们不一样。

as的作用是让编译器知道您想要将对象用作另一个对象。

在您的示例中,tableView.dequeueReusableCell会返回UITableViewCell?,这是一个可选项,可以不包含任何内容或UITableViewCell

可以通过以下几种方式展开选项:if/let

if let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") {
  // cell is a UITableViewCell here, not an optional
}
// cell doesn't exist here anymore

使用guard

guard let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") else {
// cell is nil so it's not even created. We HAVE to exit here
return 
}
// cell is a cell is UITableView here, no optional

或者通过强制它,这是不安全的,可能会导致您的应用程序崩溃。 (就我个人而言,我尽可能地避免这些,并且仅将它们用于IBOutlets

let cell = tableView.dequeueReusableCell(withIdentifier: "Cell")!
// cell will be UITableViewCell!, which is an optional that will
// crash if you try to use it and it's nil

现在回到你的问题,你有一个UITableViewCell?,这是一个常规的可选项。

在您的第一个示例中,您首先使用!强行展开它,然后将其强制转换为UITableViewCell。这将导致UITableViewCell实际上是一个强制解包的可选项,如果它是零并且您尝试使用它,它将使您的应用程序崩溃。

在第二个示例中,您告诉编译器要将UITableViewCell?用作UITableViewCell!,这将导致相同的情况。

在任何一个示例中,您都需要在使用?时使用!cell,因为它已经强制解包。

但是,我会建议你使用一个后卫来避免使用强制解包的选项。这就是我处理自定义单元格的方式:

guard let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as? CustomCell else {
  fatalError("CustomCell is not in the table view")
  // Crash with a custom message instead of a generic one
}

请注意,我正在使用tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath),此方法不返回可选项,因此如果您要使用没有自定义类的单元格,则可以使用此方法而不使用担心打开。

答案 1 :(得分:3)

好的,让我们开始按你的问题分开:

  

dequeueReusableCell(withIdentifier:)

此方法处理表视图中单元格的重用。如果没有可用的单元格,并且您没有注册类或nib文件,则此方法返回nil。

在你的第一个案例中,你是在进行演员表之前解开可选项:

let cell = tableView.dequeueReusableCell(withIdentifier: "Cell")! as UITableViewCell

那么你可以打电话:

cell.textLabel?.text = someString

无需展开为方法dequeueReusableCell(withIdentifier:)返回的可选单元格的值。

在你的第二个案例中,你没有强制解开函数dequeueReusableCell(withIdentifier:)的值。

那么你需要使用链接可选引用单元格或者强制展开可选项(链接可选项,这是建议的方法,如果可选项是nil,则避免运行时异常),如前所述。

但你可以避免所有那些只是打电话:

let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as! UITableViewCell

当然,您需要确保投射应该有效(对于我所说的自定义单元格)。所以最后,你正在玩选项,你正在使用as运算符进行向上转发和向下转发。

您可以详细了解向上转发和向下转发的差异What's the difference between "as?", "as!", and "as"?

我强烈建议你阅读UITableView Programming Guider for iOS,你可以从那里学到很多东西。

我希望这对你有所帮助。

答案 2 :(得分:2)

你是隐式展开与强制展开可选项。 当您将其类型定义为

时,可以将Optional定义为隐式展开
let x: String!

它告诉编译器自动解包该值,就好像它根本不是可选的。 另一个例子可能是:

@IBOutlet weak var someLabel: UILabel!

如果您将outlet定义为隐式解包的选项,这很常见,因为它将由Interface Builder实例化。但是,如果您忘记将此插座连接到Interface Builder,则会出现运行时错误。

对于强制解包,它包括在您的可选值之后添加!。这意味着编译器将打开它而不检查它是否为零。

与隐式展开不同,强制展开用于现有值,即您确定会有某个值,并且根本不会返回nil。

一般建议,除了!之外,永远不要使用IBOutlet。您可以在代码中使用if letguard let。永远不要认为它永远不会返回零值,因为你永远不会知道!