Swift 强制施法真的很糟糕吗?应该一直避免吗?

Swift 强制施法真的很糟糕吗?应该一直避免吗?,swift,uitableview,tableview,uicollectionviewcell,collectionview,Swift,Uitableview,Tableview,Uicollectionviewcell,Collectionview,我开始使用swiftLint,并注意到Swift的最佳实践之一是避免强制施法。但是,我在处理tableView和collectionView单元格时经常使用它: let cell = collectionView.dequeueReusableCellWithReuseIdentifier(cellID, forIndexPath: indexPath) as! MyOffersViewCell 如果这不是最佳实践,那么正确的处理方法是什么?我想我可以使用if-let和as?,但这是否意味着对

我开始使用swiftLint,并注意到Swift的最佳实践之一是避免强制施法。但是,我在处理tableView和collectionView单元格时经常使用它:

let cell = collectionView.dequeueReusableCellWithReuseIdentifier(cellID, forIndexPath: indexPath) as! MyOffersViewCell
如果这不是最佳实践,那么正确的处理方法是什么?我想我可以使用if-let和as?,但这是否意味着对于其他条件,我需要返回一个空单元格?可以接受吗

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

这个问题可能是基于观点的,所以对我的回答持保留态度,但我不会说“压力下降”总是不好的;你只需要考虑语义,以及如何在给定的情况下应用。

as!SomeClass是一个契约,它基本上说“我保证这个东西是SomeClass的一个实例”。如果结果证明它不是SomeClass,那么将抛出一个异常,因为您违反了约定

你需要考虑你使用这个合同的上下文,如果你没有使用强制降级,你可以采取什么适当的行动。 在您给出的示例中,如果
dequeueReusableCellWithIdentifier
没有为您提供
MyOffersViewCell
,则您可能错误配置了与单元重用标识符有关的内容,异常将帮助您找到该问题

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

现在,考虑一个访问Web服务中检索到的JSON的情况。web服务中可能会有超出您控制范围的更改,因此更优雅地处理该更改可能会更好。您的应用程序可能无法运行,但至少您可以显示警报,而不是简单地崩溃:

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

 let someArray=myJSON as! NSArray 
 ...
更好-使用警报处理无效JSON

guard let someArray=myJSON as? NSArray else {
    // Display a UIAlertController telling the user to check for an updated app..
    return
}
例如,当你知道你要投的是那种类型的东西时,“强制施法”就有它的位置

假设我们知道
myView
有一个子视图是带有标签
1
UILabel
,我们可以继续并强制从
UIView
UILabel
安全:

myLabel = myView.viewWithTag(1) as! UILabel
或者,更安全的选择是使用防护装置

guard let myLabel = myView.viewWithTag(1) as? UILabel else {
  ... //ABORT MISSION
}
后者更安全,因为它显然可以处理任何坏情况,但前者更容易。因此,实际上这取决于个人偏好,考虑到它是否会在未来发生变化,或者如果你不确定你正在拆开的东西是否会是你想要的东西,那么在这种情况下,一个守卫永远是正确的选择


总结:如果你确切地知道它将是什么,那么你可以强制施放,否则如果有一点点可能,它可能是其他东西,请使用一个守卫更新

使用了一段时间后,我现在完全皈依了零力解包裹崇拜(与@Kevin下面的评论一致)

在任何情况下,您都不需要强制展开无法使用的选项
if let…
guard let。。。else
,或
开关。。。请改为使用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...
    }
}
…或者,如果您更喜欢详尽的
开关
语句的优雅和安全性,而不是易于出错的
if/else
s链,那么:

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...
}
…或者更好,使用
flatMap()
mediaArray
分别转换为两个类型的数组(可能为空)类型的数组
[Song]
[Movie]
。但这超出了问题的范围(强制展开)

此外,我不会强制展开,即使在将表视图单元格出列时也是如此。如果出列单元无法转换为相应的
UITableViewCell
子类,这意味着我的故事板有问题,因此我无法从运行时条件中恢复(而是必须检测和修复的开发时错误),因此我使用
fatalError()
退出


原始答案(记录在案)

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

if myObject is String {
   let myString = myObject as! String
}
考虑Apple给出的示例:一组
媒体
实例,可以包含
歌曲
电影
对象(媒体的两个子类):


在您确实确定对象应该是指定类型的情况下,向下转换是可以的。但是,在这些情况下,我使用以下全局函数在日志中获得更有意义的结果,这在我看来是一种更好的方法:

public func castSafely<T>(_ object: Any, expectedType: T.Type) -> T {
    guard let typedObject = object as? T else {
        fatalError("Expected object: \(object) to be of type: \(expectedType)")
    }
    return typedObject
}

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

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

基本上,将其包装在
guard
语句周围,并使用
as?

选择性地强制转换。当您处理类型时,如果确保它们具有预期的类型并且始终具有值,则应强制转换。如果你的应用程序崩溃,你可以很容易地发现你在用户界面的哪一部分有错误,出列单元

但是当你要铸造你不知道的类型时,它总是相同的类型吗? 还是说这总是有价值的? 你应该避免强行展开

就像来自服务器的JSON,您不确定它是什么类型,或者其中一个键是否有值

对不起,我英语不好,我正在努力提高自己


祝您好运,如一些强制转换讨论中所述,强制对
tableView.dequeueReusableCell进行强制转换是可以的,可以/应该完成

正如Swiftlint Gits上的回答
class AnalysisViewController: UIViewController {

    var analysisView: AnalysisView {
        return castSafely(self.view, expectedType: AnalysisView.self)
    }

    override func loadView() {
        view = AnalysisView()
    }
}
guard let cell = tableView.dequeueReusableCell(
    withIdentifier: PropertyTableViewCell.reuseIdentifier,
    for: indexPath) as? PropertyTableViewCell
else {
    fatalError("DequeueReusableCell failed while casting")
}
// swiftlint:disable force_cast
let cell = tableView.dequeueReusableCell(withIdentifier: "cellOnOff", for: indexPath) as! SettingsCellOnOff
// swiftlint:enable force_cast