无法将类型“[UITableViewCell.Type]”的值转换为 Swift 4.2 中的“[_.Type]”

Cannot convert value of type '[UITableViewCell.Type]' to '[_.Type]' in Swift 4.2

嗯,今天我将 Xcode 更新到 10.0 版后,我的代码遇到了以下错误。

// error: Cannot convert value of type '[UITableViewCell.Type]' to expected argument type
// '[_.Type]'  
table.registerCells(cells: [MainMenuTableViewCell.self,
                            RescueServicesTableViewCell.self])

这里是 registerCells 函数:

func registerCells<T> (cells : [T.Type]) where T: UITableViewCell  {
        for cell in cells  {
            let nib = UINib(nibName: String(describing: cell), bundle: nil)
            register(nib, forCellReuseIdentifier: String(describing: cell))
        }
    }

一开始我认为这可能是 swift 重新版本控制问题所以我从 swift 3 转换为 swift 4 并在 2 小时后花时间修复语法错误一直在那里,直到我变魔术。

let cellItems = [MainMenuTableViewCell.self,
                 RescueServicesTableViewCell.self]

table.registerCells(cells:cellItems)

此解决方案有效且错误消失。现在我的问题是为什么我收到此错误是 Xcode 问题还是我做错了什么?

This is an interesting bug (SR-8825) where the compiler appears to be unable to perform type joining (the process of inferring a common supertype for a collection of types) within a member access on an implicitly unwrapped optional (IUO) declaration (presumably in your case table is an IUO @IBOutlet).

A minimal example would be:

class C {}
class D : C {}
class E : C {}

struct X {
  func foo<T>(_: [T.Type]) where T : C {}
}

var x: X!

// error: Cannot convert value of type '[C.Type]' to expected argument type '[_.Type]'
x.foo([D.self, E.self]) 

Making x either non-optional, or a strong optional (i.e X?) while performing either optional chaining (i.e x?.foo) or force unwrapping (i.e x!.foo) to perform the member access allows the code to compile.


There are a few workarounds you can use, first of which is to explicitly specify the array type, saving the compiler from having to infer the type join:

x.foo([D.self, E.self] as [C.Type])

In your case, this translates to:

table.registerCells(cells: 
  [MainMenuTableViewCell.self, RescueServicesTableViewCell.self] as [UITableViewCell.Type]
)

Second workaround is to use a non-optional base. In your case, you can force unwrap the IUO into a local variable before performing the member access:

// this is just explicitly doing what the compiler would have otherwise done implicitly.
let table = self.table!
table.registerCells(cells: [MainMenuTableViewCell.self, RescueServicesTableViewCell.self])

Third workaround is, as you've already discovered, to separate out the array into its own expression – which allows the compiler to do the type joining on its own:

let cellItems = [MainMenuTableViewCell.self, RescueServicesTableViewCell.self]
table.registerCells(cells: cellItems)

Though the solution I would go with in your case is to make registerCells(cells:) non-generic, as it doesn't appear that you're using the generic placeholder T for anything useful:

extension UITableView {
  func registerCells(_ cells: [UITableViewCell.Type]) {
    for cell in cells  {
      let nib = UINib(nibName: String(describing: cell), bundle: nil)
      register(nib, forCellReuseIdentifier: String(describing: cell))
    }
  }
}

Which you can now just call like so:

table.registerCells([MainMenuTableViewCell.self, RescueServicesTableViewCell.self])
tableView.dequeueReusableCell(withIdentifier: String(describing: YoursCell.self), for: indexPath)