我使用以下代码填充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新手,它很难吸收选项,这种例子让我更加困惑。有人能解释一下这里发生了什么吗?代码是否相同?并且一种可选的风格比另一种更令人满意吗?
答案 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 let
或guard let
。永远不要认为它永远不会返回零值,因为你永远不会知道!