【问题标题】:How to avoid force casting (as!) in Swift如何避免在 Swift 中强制转换(as!)
【发布时间】:2021-04-23 00:34:16
【问题描述】:
extension ActionSheetViewController: UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return sheetActions.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

        let cell = tableView.dequeueReusableCell(withIdentifier: TableCellIds.ActionSheet.actionSheetTableCellIdentifier, for: indexPath) as! ActionsSheetCell

        cell.actionCellLabel.text = "My cell content goes here"
        return cell
    }
}

上面的代码给了我'强制强制转换:应该避免强制强制转换。 (force_cast)' 错误。如何避免?

【问题讨论】:

    标签: swift xcode tableview


    【解决方案1】:

    一些强制转换是不可避免的,尤其是在与具有更动态/松散类型系统的 Objective C 交互时。

    在某些情况下,强制转换是不言自明的。如果它崩溃了,显然你是:

    • 返回nil(意味着没有具有该重用标识符的视图),
    • 或者您返回的类型错误(意味着单元格存在,但您重新配置了其类型)。

    在任何一种情况下,您的应用都严重错误配置,除了首先修复错误之外,您无法进行任何优雅的恢复。

    对于这个特殊的上下文,我使用了这样的辅助扩展(它适用于 AppKit,但很容易适应)。它检查上述两个条件,并呈现更有用的错误消息。

    public extension NSTableView {
        /// A helper function to help with type-casting the result of `makeView(wihtIdentifier:owner:)`
        /// - Parameters:
        ///   - id: The `id` as you would pass to `makeView(wihtIdentifier:owner:)`
        ///   - owner: The `owner` as you would pass to `makeView(wihtIdentifier:owner:)`
        ///   - ofType: The type to which to cast the result of `makeView(wihtIdentifier:owner:)`
        /// - Returns: The resulting view, casted to a `T`. It's not an optional, since that type error wouldn't really be recoverable
        ///            at runtime, anyway.
        func makeView<T>(
            withIdentifier id: NSUserInterfaceItemIdentifier,
            owner: Any?,
            ofType: T.Type
        ) -> T {
            guard let view = self.makeView(withIdentifier: id, owner: owner) else {
                fatalError("This \(type(of: self)) didn't have a column with identifier \"\(id.rawValue)\"")
            }
            
            guard let castedView = view as? T else {
                fatalError("""
                Found a view for identifier \"\(id.rawValue)\",
                    but it had type:           \(type(of: view))
                    and not the expected type: \(T.self)
                """)
            }
            
            return castedView
        }
    }
    

    老实说,在我对 NSTableView API 有足够的经验后,调查这些问题已成为第二天性,我不觉得这个扩展有用。不过,它可以为新平台的开发人员节省一些调试和挫败感。

    【讨论】:

    • 我修改了我的答案,加入了你的扩展之类的东西。
    【解决方案2】:

    在这种情况下强制施放实际上是正确的。 这里的重点是,如果你不能做演员,你真的不想继续,因为你必须返回一个真实的单元格,如果它是错误的类,应用程序就会损坏并且你没有单元格,所以崩溃是好的.

    但是 linter 并没有意识到这一点。解决这个问题的常用方法是在as? 中使用guard let,在else 中使用fatalError。这具有相同的效果,并且 linter 会接受它。

    我真的很喜欢 Alexander 在 https://stackoverflow.com/a/67222587/341994 建议的方法 - 这是它的 iOS 修改:

    extension UITableView {
        func dequeue<T:UITableViewCell>(withIdentifier id:String, for ip: IndexPath) -> T {
            guard let cell = self.dequeueReusableCell(withIdentifier: id, for: ip) as? T else {
                fatalError("could not cast cell")
            }
            return cell
        }
    }
    

    所以现在你可以说例如:

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell : MyTableViewCell = tableView.dequeue(withIdentifier: "cell", for: indexPath)
        // ...
        return cell
    }
    

    每个人都很高兴,包括 linter。由于泛型和显式类型声明,不会在任何地方强制解包,并且会自动执行强制转换。

    【讨论】:

      【解决方案3】:

      正如其他人所说,在这种情况下强制转换是合适的,因为如果它失败,则意味着您的源代码中存在严重错误。

      要使 SwiftLint 接受强制转换,您可以按照 in this issue in the SwiftLint repo 所述将语句用 cmets 括起来:

      // swiftlint:disable force_cast
      let cell = tableView.dequeueReusableCell(withIdentifier: TableCellIds.ActionSheet.actionSheetTableCellIdentifier, for: indexPath) as! ActionsSheetCell
      // swiftlint:enable force_cast
      

      【讨论】:

        【解决方案4】:

        正确的做法是:从 swift lint 的配置文件中删除 force_cast。并且要专业:仅在您的意思是“展开或致命错误”的地方编写强制转换。不得不“绕过 linter”是对开发人员时间的毫无意义的浪费。

        【讨论】:

          【解决方案5】:

          更新的解决方案:

          extension ActionSheetViewController: UITableViewDataSource {
              func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
                  return sheetActions.count
              }
          
              func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
                  guard let cell = tableView.dequeueReusableCell(withIdentifier: TableCellIds.ActionSheet.actionSheetTableCellIdentifier, for: indexPath) as? ActionsSheetCell
                      else {
                          fatalError("Could not find a cell with the id, or its type was not ActionsSheetCell")
                      }
                  cell.actionCellLabel.text = sheetActions[indexPath.row].action
                  return cell
              }
          }
          

          【讨论】:

          • 这显然更糟。如果您在故事板中错误地配置了单元格的类(例如,您没有标识符为 TableCellIds.ActionSheet.actionSheetTableCellIdentifier 的单元格,或者您将其类设置为 ActionsSheetCell 以外的其他值),那么这将只是默默地和神秘地什么都不做。如果您不知道要注意这种错误,则需要很长时间才能跟踪和调试。
          • 如果你坚持要避免强制解包,无论如何(我建议不要这样做),最好是 fatalError 提供一个易于阅读的信息:guard let cell = ... as? ActionsSheetCell else { fatalError("Could not find a cell with the id \(id), or its type was not ActionsSheetCell") ) }
          • 那甚至不会编译,因为你不能在这里返回一个 Optional。
          • 对不起,我意识到我不小心错过了最后一行代码 - 添加。现在正在编译。
          • @marika.daboja 这仍然没有意义。为什么要cell?.actionCellLabel.text = (意思是“如果它为零,什么也不做”),如果在下一行你要执行cell!(意思是“我坚持它不是零,但如果是,请崩溃” )。您也可以尽早强制解包一次,然后使用您配置并返回的非可选项从那里继续。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2014-01-06
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-12-29
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多