强制施放真的很糟糕并且应该始终避免吗?

Is force cast really bad and should always avoid it?

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

let cell = collectionView.dequeueReusableCellWithReuseIdentifier(cellID, forIndexPath: indexPath) as! MyOffersViewCell

如果这不是最佳做法,那么正确的处理方法是什么?我想我可以使用 if let with as?,但这是否意味着在 else 条件下我需要 return 一个空单元格?可以接受吗?

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

这个问题可能是基于个人意见的,所以对我的回答持保留态度,但我不会说强制向下 总是 不好;您只需要考虑语义及其在给定情况下的应用方式。

as! SomeClass是合同,基本上就是"I guarantee that this thing is an instance of 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
}

"Force Cast" 有它的位置,例如,当您 知道 您要投射到的是那种类型时。

假设我们知道 myView 有一个 UILabel 的子视图,带有标签 1,我们可以继续强制从 UIView 向下转换为 UILabel 安全:

myLabel = myView.viewWithTag(1) as! UILabel

或者,更安全的选择是使用守卫。

guard let myLabel = myView.viewWithTag(1) as? UILabel else {
  ... //ABORT MISSION
}

后者更安全,因为它显然可以处理任何不良情况,但前者更容易。所以这实际上归结为个人偏好,考虑它是否会在未来发生变化,或者如果你不确定你正在解开的东西是否是你想要投射到的东西那么在那种情况下守卫总是正确的选择。

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

更新

使用 Swiftlint 一段时间后,我现在完全皈依了零力展开崇拜(与下面@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...
    }
}

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

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变成两个(可能是空的)typed类型的数组[Song][Movie] 分别。但这超出了问题的范围(force-unwrap)...

此外,即使在 table 视图单元格出队时,我也不会强制解包。如果无法将出队的单元格转换为适当的 UITableViewCell 子类,则意味着我的故事板有问题,所以这不是我可以从中恢复的运行时条件(而是必须检测到的开发时错误,并且固定)所以我用 fatalError() 保释。


原答案(备案)

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

if myObject is String {
   let myString = myObject as! String
}

考虑 Apple 给出的示例:Media 个实例的数组,可以包含 SongMovie 个对象(都是 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...
   }

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

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
}

用法示例:

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")
}

基本上,将其包裹在 guard 语句中,并可选择使用 as?.

进行转换

当您使用您的类型并确定它们具有预期的类型并且始终具有值时,它应该强制转换。如果您的应用程序崩溃,您可以很容易地发现您在 UI、Dequeuing Cell、...

的哪个部分出错

但是当你要转换你不知道的类型时,它总是相同的类型吗? 还是那总是有价值的? 你应该避免强制解包

例如来自服务器的 JSON,您不确定该服务器是什么类型或其中一个键是否具有值

抱歉我的英语不好我正在努力提高自己

祝你好运

如某些选角讨论中所述,强制选角 tableView.dequeueReusableCell 没问题,can/should 完成。

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

Link to Swiftlink issue 145

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