强制施展真的很糟糕,应该总是避免吗?

use*_*111 38 uitableview tableview collectionview uicollectionviewcell swift

我开始使用swiftLint并注意到Swift的最佳实践之一是避免使用强制转换.但是在处理tableView,collectionView for cells时我经常使用它:

let cell = collectionView.dequeueReusableCellWithReuseIdentifier(cellID, forIndexPath: indexPath) as! MyOffersViewCell
Run Code Online (Sandbox Code Playgroud)

如果这不是最好的做法,那么处理这个问题的正确方法是什么?我想我可以使用if with as?,但这是否意味着其他条件我需要返回一个空单元格?那可以接受吗?

if let cell = collectionView.dequeueReusableCellWithReuseIdentifier(cellID, forIndexPath: indexPath) as? MyOffersViewCell {
      // code
} else {
      // code
}
Run Code Online (Sandbox Code Playgroud)

Pau*_*w11 54

这个问题可能是以意见为基础的,所以我的答案是一丝不苟,但我不会说那种被迫贬低的人总是很糟糕; 您只需要考虑语义以及在给定情况下如何应用.

as! SomeClass它是一个契约,它基本上说"我保证这个东西是SomeClass的一个实例".如果事实证明它不是SomeClass,则会因为违反合同而抛出异常.

您需要考虑使用此合同的上下文以及如果不使用强制转发,可以采取的适当措施.

在您给出的示例中,如果dequeueReusableCellWithIdentifier 没有给您,MyOffersViewCell那么您可能错误配置了与单元重用标识符有关的内容,并且异常将帮助您找到该问题.

如果您使用了条件下转,那么您将获得nil并且必须以某种方式处理 - 记录消息?抛出异常?它肯定代表了一个不可恢复的错误,你想在开发过程中找到它; 你不希望在发布后必须处理这个问题.您的代码不会突然开始返回不同类型的单元格.如果你只是让代码在强制转发时崩溃,它将直接指向发生问题的行.

现在,考虑一种情况,即您正在访问从Web服务检索的某些JSON.Web服务可能会发生变化,超出您的控制范围,因此更优雅地处理此服务可能会很好.您的应用可能无法运行,但至少您可以显示警报而不是简单地崩溃:

BAD - 如果JSON不是数组,则崩溃

 let someArray=myJSON as! NSArray 
 ...
Run Code Online (Sandbox Code Playgroud)

更好 - 使用警报处理无效的JSON

guard let someArray=myJSON as? NSArray else {
    // Display a UIAlertController telling the user to check for an updated app..
    return
}
Run Code Online (Sandbox Code Playgroud)

  • 如果您正在使用SwiftLint并且只想使违规静音:让someArray =(myJSON as?NSArray)! (3认同)

Nic*_*ari 19

更新

在使用Swiftlint一段时间后,我现在完全转换为Zero Force-Unwrapping Cult(符合下面@ Kevin的评论).

是不是真的有你需要强制解开一个可选的,你不能使用任何情况下if let...,guard let... elseswitch... case let...代替.

所以,现在我会这样做:

for media in mediaArray {
    if let song = media as? Song {
        // use Song class's methods and properties on song...

    } else if let movie = media as? Movie {
        // use Movie class's methods and properties on movie...
    }
}
Run Code Online (Sandbox Code Playgroud)

...或者,如果您更喜欢详尽switch陈述的优雅和安全性,而不是容易出错的if/elses 链,那么:

switch media {
case let song as Song:
    // use Song class's methods and properties on song...
case let movie as Movie:    
    // use Movie class's methods and properties on movie...
default:
    // Deal with any other type as you see fit...
}
Run Code Online (Sandbox Code Playgroud)

...或更好,使用flatMap()mediaArray成两个(可能是空的)类型的类型的阵列[Song][Movie]分别.但这超出了问题的范围(强制解包)......

此外,即使将表视图单元格出列,我也不会强制解包.如果无法将出队的单元格强制转换为相应的UITableViewCell子类,这意味着我的故事板出现了问题,因此我无法恢复某些运行时条件(相反,必须检测并修复了开发时错误)所以我保释与fatalError().


原始答案(备案)

除了Paulw11的答案之外,这种模式有时是完全有效,安全和有用的:

if myObject is String {
   let myString = myObject as! String
}
Run Code Online (Sandbox Code Playgroud)

考虑Apple给出的示例:一个Media实例数组,可以包含Song或者Movie对象(Media的两个子类):

let mediaArray = [Media]()

// (populate...)

for media in mediaArray {
   if media is Song {
       let song = media as! Song
       // use Song class's methods and properties on song...
   }
   else if media is Movie {
       let movie = media as! Movie
       // use Movie class's methods and properties on movie...
   }
Run Code Online (Sandbox Code Playgroud)

  • 我更喜欢在这里展开而不是强行施放.`如果让myString = myObject为?String`或`if let song = media as?歌曲{}否则,如果让电影=媒体?Movie`.虽然该模式是安全的,但是可以在没有强制展开的情况下完成可选的展开 (2认同)

Bla*_*ley 5

当你知道你所投射的是那种类型时,"强制施放"就有它的位置.

假设我们知道myView有一个UILabel带有标记的子视图1,我们可以继续强制执行UIViewUILabel安全:

myLabel = myView.viewWithTag(1) as! UILabel
Run Code Online (Sandbox Code Playgroud)

或者,更安全的选择是使用警卫.

guard let myLabel = myView.viewWithTag(1) as? UILabel else {
  ... //ABORT MISSION
}
Run Code Online (Sandbox Code Playgroud)

后者更安全,因为它显然可以处理任何不良情况,但前者更容易.所以真的归结为个人偏好,考虑到它的某些东西是否会在将来发生变化,或者如果你不确定你所展开的东西是否是你想要的东西,那么在那种情况下,卫兵将永远是正确的选择.

总结一下:如果你确切地知道它会是什么,那么你可以强制施放,否则如果有可能是其他东西使用后卫


Car*_*ann 5

其他人写了一个更一般的案例,但我想给出我对这个确切案例的解决方案:

guard let cell = tableView.dequeueReusableCell(
    withIdentifier: PropertyTableViewCell.reuseIdentifier,
    for: indexPath) as? PropertyTableViewCell
else {
    fatalError("DequeueReusableCell failed while casting")
}
Run Code Online (Sandbox Code Playgroud)

基本上,将它包裹在一个guard语句中,并可选地使用as?.

  • 我会争辩说这比强制沮丧有用。您仍然会崩溃,但不是告诉您诸如“OtherCellClass 无法向下转换为 PropertyTableViewCell”之类的异常,它告诉您确切发生了什么,您会收到“DequeueResuableCell 在转换时失败”,然后您需要去调查原因 (5认同)
  • @Paulw11 我同意,使用fatalError 没有意义。您可以改用 assertionFailure() 并返回空单元格;这样,您的生产构建是安全的,以防您不小心更改了标识符字符串或其他内容。 (2认同)

小智 5

正如一些选角讨论中所述,强制选角tableView.dequeueReusableCell是可以的,并且可以/应该这样做。

正如 Swiftlint Github 网站上的回答,您可以使用一种简单的方法来关闭它以进行表格单元格强制转换。

链接到 Swiftlink 第 145 期

// swiftlint:disable force_cast
let cell = tableView.dequeueReusableCell(withIdentifier: "cellOnOff", for: indexPath) as! SettingsCellOnOff
// swiftlint:enable force_cast
Run Code Online (Sandbox Code Playgroud)