问题
I already posted a question but it was not clear about what I want. As @AlainT suggested, I filed a new one.
I have a typealias tuple
public typealias MyTuple<T> = (key: T, value: String)
A protocol:
public protocol VCADelegate: class {
associatedtype T
func didSelectData(_ selectedData: MyTuple<T>)
}
A view controller (VCA) with a table view
class VCA<T>: UIViewController, UITableViewDelegate, UITableViewDataSource {
var dataList = [MyTuple<T>]()
weak var delegate: VCADelegate? // Error: can only be used as a generic constraint
// ...
public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
delegate?.didSelectData(dataList[indexPath.row])
}
}
In another view controller (VCB), I create a VCA and pass through a dataList
func callVCA() {
let vcA = VCA<String>()
vcA.dataList = [(key: "1", value:"Value 1"),
(key: "2", value:"Value 2")]
}
What I want to do is to have a dataList without knowing key's data type in VCA. Only when VCB calls VCA then I know the data type of the key. Creating a generic view controller will cause an issue with delegate. Any way to solve this problem without having to change to closure completion?
The other issue of using a generic view controller is I can't extend it. Any idea?
回答1:
This is a standard type-erasure situation, though in this particular case I'd just pass a closure (since there's only one method).
Create a type eraser instead of a protocol:
public struct AnyVCADelegate<T> {
let _didSelectData: (MyTuple<T>) -> Void
func didSelectData(_ selectedData: MyTuple<T>) { _didSelectData(selectedData)}
init<Delegate: VCADelegate>(delegate: Delegate) where Delegate.T == T {
_didSelectData = delegate.didSelectData
}
}
Use that instead of a delegate:
class VCA<T>: UIViewController, UITableViewDataSource UITableViewDelegate {
var dataList = [MyTuple<T>]()
var delegate: AnyVCADelegate<T>?
// ...
public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
delegate?.didSelectData(dataList[indexPath.row])
}
}
Your underlying problem is that protocols with associated types are not proper types themselves. They're only type constraints. If you want to keep it a PAT, that's fine, but then you have to make VCA
generic over the Delegate:
class VCA<Delegate: VCADelegate>: UIViewController, UITableViewDelegate {
var dataList = [MyTuple<Delegate.T>]()
weak var delegate: Delegate?
init(delegate: Delegate?) {
self.delegate = delegate
super.init(nibName: nil, bundle: nil)
}
required init(coder: NSCoder) { super.init(nibName: nil, bundle: nil) }
public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
delegate?.didSelectData(dataList[indexPath.row])
}
}
class VCB: UIViewController, VCADelegate {
func didSelectData(_ selectedData: MyTuple<String>) {}
func callVCA() {
let vcA = VCA(delegate: self)
vcA.dataList = [(key: "1", value:"Cinnamon"),
(key: "2", value:"Cloves")]
}
}
As a rule, protocols with associated types (PATs) are a very powerful, but special-purpose tool. They aren't a replacement for generics (which are a general purpose tool).
For this particular problem, though, I'd probably just pass a closure. All a type eraser is (usually) is a struct filled with closures. (Some day the compiler will probably just write them for us, and much of this issue will go away and PATs will be useful in day-to-day code, but for now it doesn't.)
来源:https://stackoverflow.com/questions/44490152/generic-view-controller-wont-work-with-delegate-and-extension