Bil*_*ill 10 generics uitableview ios swift
我的应用程序有一个所有表控制器的公共基类,当我定义该表控制器基类的通用子类时,我遇到了一个奇怪的错误.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 {
}
Run Code Online (Sandbox Code Playgroud)
这是非常简单的通用子类:
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)
}
}
Run Code Online (Sandbox Code Playgroud)
如果我创建一个简单的应用程序除了显示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
}
}
Run Code Online (Sandbox Code Playgroud)
表格绘制正确但numberOfSections(in:)从未调用过!因此,该表仅显示一个部分(可能是因为根据文档,UITableView如果未实现该方法,则使用1作为此值).
但是,如果我从类中删除泛型声明:
class ViewController: CustomTableViewController {
let data: Int
init(data: Int) {
....
}
// ...
}
Run Code Online (Sandbox Code Playgroud)
然后numberOfSections被召唤!
这种行为对我没有任何意义.我可以通过定义numberOfSectionsin CustomTableViewController然后ViewController明确覆盖该函数来解决它,但这似乎不是正确的解决方案:我必须为任何UITableViewDataSource具有此问题的方法执行此操作.
And*_*jen 11
这是swift通用子系统中的一个错误/缺点,与可选(因此:) @objc协议功能相结合.
先解决问题
您必须@objc为子类中的所有可选协议实现指定.如果Objective C选择器和swift函数名之间存在命名差异,则还必须在parantheses中指定Objective C选择器名称@objc (numberOfSectionsInTableView:)
@objc (numberOfSectionsInTableView:)
func numberOfSections(in tableView: UITableView) -> Int {
// this is now called!
print("called numberOfSections")
return 1
}
Run Code Online (Sandbox Code Playgroud)
对于非泛型子类,这已在Swift 4中修复,但显然不适用于泛型子类.
复制
您可以在操场上轻松复制:
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)")
}
}
Run Code Online (Sandbox Code Playgroud)
现在当我们在没有泛型的情况下使用它时,一切都可以找到:
let normal = NormalSubclass(val:42)
normal.doIt() // Doing 42
normal.baseMethod() // Doing 42
Run Code Online (Sandbox Code Playgroud)
使用通用子类时,baseMethod调用崩溃:
let generic = GenericSubclass(val:5)
generic.doIt() // Doing 5
generic.baseMethod() // error: Execution was interrupted, reason: signal SIGABRT.
Run Code Online (Sandbox Code Playgroud)
有趣的doIt是GenericSubclass,虽然我们之前刚刚调用过选择器,但是找不到选择器:
2018-01-14 22:23:16.234745 + 0100 GenericTableViewControllerSubclass [13234:3471799] - [ TtGC34GenericTableViewControllerSubclass15GenericSubclassSi doIt]:无法识别的选择器发送到实例0x60800001a8d0 2018-01-14 22:23:16.243702 + 0100 GenericTableViewControllerSubclass [13234:3471799]***由于未捕获的异常'NSInvalidArgumentException'终止应用程序,原因:' - [ TtGC34GenericTableViewControllerSubclass15GenericSubclassSi doIt]:无法识别的选择器发送到实例0x60800001a8d0'
(从"真实"项目中获取的错误消息)
所以无论如何都找不到选择器(例如Objective C方法名).解决方法:@objc像以前一样添加到子类.在这种情况下,我们甚至不需要指定不同的方法名称,因为swift func名称等于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
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
541 次 |
| 最近记录: |