Generic Class does not forward delegate calls to c

2020-06-16 01:22发布

问题:

Given the following.

protocol EntityType {
    var displayString: String { get }
}

extension String: EntityType {
    var displayString: String { return self }
}

class GenericListViewController<Entity>: UIViewController, UITableViewDataSource, UITableViewDelegate where Entity: EntityType {
    let items: [Entity]

    let tableView: UITableView

    init(items: [Entity]) {
        self.items = items
        self.tableView = UITableView()

        super.init(nibName: nil, bundle: nil)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func loadView() {
        super.loadView()

        tableView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(tableView)

        NSLayoutConstraint.activate([
            tableView.topAnchor.constraint(equalTo: view.topAnchor),
            tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
            tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor)
        ])

        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
        tableView.dataSource = self
        tableView.delegate = self

    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return items.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
        cell.textLabel?.text = items[indexPath.row].displayString

        return cell
    }

//    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
//        
//    }
}

class StringListViewController: GenericListViewController<String> {
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        print("selected: \(items[indexPath.row])")
    }
}

Why isn't tableView:didSelectRowAt: being called in the concrete subclass? This works fine for non-generic types or when the parent class has an implementation and the sub-class overrides it. Is this expected behavior, Swift bug or am I missing something?

回答1:

It does seem like a possible bug. My guess it that it might have something to do with the fact that Swift generics are not visible to the Objective-C runtime, so even though your methods implemented directly in GenericListViewController<Entity> are being called, there may be some buggy behavior between the Swift and Obj-C runtimes trying to figure out overrides. Definitely worth a bug report.

I will note though, that in strict OOP abstract superclasses generally do not conform to protocols themselves, they simply provide default implementations. It's still up to the concrete subclasses to declare protocol conformance and fill in any missing implementations.

In the case of your code above, your GenericListViewController<Entity> class should not conform to UITableViewDataSource or UITableViewDelegate. It simply provides the default method implementations that would allow a concrete subclass to conform without having to rewrite those method implementations (unless override is desired). Your StringListViewController should be the one declaring conformance to the two protocols. If you modify your code to do that, it will actually work as expected.

This doesn't change the fact that you have probably discovered a bug in Swift / Obj-C interop, though. I believe that what you have currently should work, although it's not the strict OOP way of handling protocol conformance.