我的应用程序有一个用于所有表 Controller 的公共(public)基类,当我定义该表 Controller 基类的通用子类时,我遇到了一个奇怪的错误。当且仅当我的子类是通用的时,方法 numberOfSections(in:)
永远不会被调用。
下面是我能做出的最小复制品:
class BaseTableViewController: UIViewController {
let tableView: UITableView
init(style: UITableViewStyle) {
self.tableView = UITableView(frame: .zero, style: style)
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: - Overridden methods
override func viewDidLoad() {
super. viewDidLoad()
self.tableView.frame = self.view.bounds
self.tableView.delegate = self
self.tableView.dataSource = self
self.view.addSubview(self.tableView)
}
}
extension BaseTableViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
return UITableViewCell(style: .default, reuseIdentifier: nil)
}
}
extension BaseTableViewController: UITableViewDelegate {
}
这是非常简单的通用子类:
class ViewController<X>: BaseTableViewController {
let data: X
init(data: X) {
self.data = data
super.init(style: .grouped)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func numberOfSections(in tableView: UITableView) -> Int {
// THIS IS NEVER CALLED!
print("called numberOfSections")
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("called numberOfRows for section \(section)")
return 2
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
print("cellFor: (\(indexPath.section), \(indexPath.row))")
let cell = UITableViewCell(style: .default, reuseIdentifier: nil)
cell.textLabel!.text = "foo \(indexPath.row) \(String(describing: self.data))"
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("didSelect: (\(indexPath.section), \(indexPath.row))")
self.tableView.deselectRow(at: indexPath, animated: true)
}
}
如果我创建一个简单的应用程序,它只显示 ViewController:
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
self.window = UIWindow(frame: UIScreen.main.bounds)
let nav = UINavigationController(rootViewController: ViewController(data: 3))
self.window?.rootViewController = nav
self.window?.makeKeyAndVisible()
return true
}
}
表格绘制正确但 numberOfSections(in:)
从未被调用!因此,该表仅显示一个部分(可能是因为根据文档,如果未实现该方法,UITableView
使用 1 作为此值)。
但是,如果我从类中删除通用声明:
class ViewController: CustomTableViewController {
let data: Int
init(data: Int) {
....
}
// ...
}
然后 numberOfSections
确实被调用了!
这种行为对我来说没有任何意义。我可以通过在 CustomTableViewController
中定义 numberOfSections
然后让 ViewController
显式覆盖该函数来解决这个问题,但这似乎不是正确的解决方案: 对于 UITableViewDataSource
中存在此问题的任何方法,我都必须这样做。
最佳答案
这是 swift 的通用子系统中的一个错误/缺点,与可选的(因此:@objc
)协议(protocol)函数一起使用。
解决方案第一
您必须为子类中的所有 可选协议(protocol)实现指定@objc
。如果 Objective C 选择器和 swift 函数名称之间存在命名差异,您还必须在括号中指定 Objective C 选择器名称,例如 @objc (numberOfSectionsInTableView:)
@objc (numberOfSectionsInTableView:)
func numberOfSections(in tableView: UITableView) -> Int {
// this is now called!
print("called numberOfSections")
return 1
}
对于非泛型子类,这已经在 Swift 4 中得到修复,但显然不是针对泛型子类。
复制
您可以在 playground 中很容易地重现它:
import Foundation
@objc protocol DoItProtocol {
@objc optional func doIt()
}
class Base : NSObject, DoItProtocol {
func baseMethod() {
let theDoer = self as DoItProtocol
theDoer.doIt!() // Will crash if used by GenericSubclass<X>
}
}
class NormalSubclass : Base {
var value:Int
init(val:Int) {
self.value = val
}
func doIt() {
print("Doing \(value)")
}
}
class GenericSubclass<X> : Base {
var value:X
init(val:X) {
self.value = val
}
func doIt() {
print("Doing \(value)")
}
}
现在当我们在没有泛型的情况下使用它时,一切正常发现:
let normal = NormalSubclass(val:42)
normal.doIt() // Doing 42
normal.baseMethod() // Doing 42
使用通用子类时,baseMethod
调用崩溃:
let generic = GenericSubclass(val:5)
generic.doIt() // Doing 5
generic.baseMethod() // error: Execution was interrupted, reason: signal SIGABRT.
有趣的是,doIt
选择器在GenericSubclass
中找不到,尽管我们之前只是调用了它:
2018-01-14 22:23:16.234745+0100 GenericTableViewControllerSubclass[13234:3471799] -[TtGC34GenericTableViewControllerSubclass15GenericSubclassSi doIt]: unrecognized selector sent to instance 0x60800001a8d0 2018-01-14 22:23:16.243702+0100 GenericTableViewControllerSubclass[13234:3471799] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[TtGC34GenericTableViewControllerSubclass15GenericSubclassSi doIt]: unrecognized selector sent to instance 0x60800001a8d0'
(取自“真实”项目的错误信息)
因此无法找到选择器(例如 Objective C 方法名称)。
解决方法:像以前一样,将 @objc
添加到子类中。在这种情况下,我们甚至不需要指定一个不同的方法名称,因为 swift 函数名称等于 Objective C 选择器名称:
class GenericSubclass<X> : Base {
var value:X
init(val:X) {
self.value = val
}
@objc
func doIt() {
print("Doing \(value)")
}
}
let generic = GenericSubclass(val:5)
generic.doIt() // Doing 5
generic.baseMethod() // Doing 5
关于ios - 自定义 TableView Controller 的通用子类的调度问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48215689/