Ваша ошибка в протоколе:
protocol GenericTableControllerDelegate: AnyObject {
func controller<T>(controller: GenericTableController<T>, didSelect value: T)
}
Это говорит о том, что для того, чтобы быть GTCD, тип должен принять любой тип T
, переданный этой функции.Но вы не это имеете в виду.Вы имели в виду следующее:
public protocol GenericTableControllerDelegate: AnyObject {
associatedtype DataType
func controller(controller: GenericTableController<DataType>, didSelect value: DataType)
}
А затем вы хотели, чтобы DataType делегата соответствовал DataType табличного представления.И это погружает нас в мир PAT (протоколов со связанными типами), стирающих типов и обобщенных экзистенциалов (которых еще нет в Swift), и на самом деле это просто беспорядок.
Несмотря на то, что это вариант использования, для которого обобщенные экзистенциалы особенно хорошо подходят (если они когда-либо добавляются в Swift), во многих случаях вы, вероятно, вообще не хотите этого.Шаблон делегирования является шаблоном ObjC, разработанным до добавления замыканий.Раньше было очень трудно передавать функции в ObjC, поэтому даже очень простые обратные вызовы превращались в делегатов.В большинстве случаев, я думаю, что подход Ричарда Топчия абсолютно правильный.Просто передайте функцию.
Но что, если вы действительно хотите сохранить стиль делегата?Мы можем (почти) сделать это.Единственный недостаток в том, что вы не можете иметь свойство с именем delegate
.Вы можете установить его, но не можете получить его.
open class GenericTableController<DataType>: UITableViewController
{
// This is the function to actually call
private var didSelect: ((DataType) -> Void)?
// We can set the delegate using any implementer of the protocol
// But it has to be called `controller.setDelegate(self)`.
public func setDelegate<Delegate: GenericTableControllerDelegate>(_ d: Delegate?)
where Delegate.DataType == DataType {
if let d = d {
didSelect = { [weak d, weak self] in
if let self = self { d?.controller(controller: self, didSelect: $0) }
}
} else {
didSelect = nil
}
}
var data = [DataType]()
// and here, just call our internal method
open override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let item = data[indexPath.row]
didSelect?(item)
}
}
Это полезный метод для понимания, но я, вероятно, не буду использовать его в большинстве случаев.Определенно возникает головная боль, когда вы добавляете больше методов, если эти методы ссылаются на DataType.Вам нужно много шаблонов.Обратите внимание, что из-за передачи self
методу делегата возникает некоторая путаница.Это то, что нужно методам делегата, но замыкания не нужны (вы всегда можете захватить контроллер в замыкании, если оно нужно).
По мере того, как вы исследуете такой код многократного использования, я призываю вас больше думать об инкапсуляциистратегии, а не об объектах и протоколах делегатов.Примером инкапсуляции стратегии может служить тип SelectionHandler, который вы передаете контроллеру:
struct SelectionHandler<Element> {
let didSelect: (Element) -> Void
}
С этим вы можете создавать простые стратегии, такие как «print it out»:
extension SelectionHandler {
static func printSelection() -> SelectionHandler {
return SelectionHandler { print($0) }
}
}
Или, что более интересно, обновите метку:
static func update(label: UILabel) -> SelectionHandler {
return SelectionHandler { [weak label] in label?.text = "\($0)" }
}
Итак, вы получите код вроде:
controller.selectionHandler = .update(label: self.nameLabel)
Или, что еще более интересно, вы можете создавать типы более высокого порядка:
static func combine(_ handlers: [SelectionHandler]) -> SelectionHandler {
return SelectionHandler {
for handler in handlers {
handler.didSelect($0)
}
}
}
static func trace(_ handler: SelectionHandler) -> SelectionHandler {
return .combine([.printSelection(), handler])
}
controller.selectionHandler = .trace(.update(label: self.nameLabel))
Этот подход сложнее, чем делегирование, и начинает раскрывать реальные преимущества Swift.