Так что я не знаю, правильно ли заголовок решает мою проблему, но вот в чем дело:

У меня есть перечисление для определения нескольких типов ячеек табличного представления. Каждому типу клетки соответствует свой класс. Я согласовал каждый из них с протоколом, но с соответствующим типом. Теперь я пытаюсь создать способ иметь экземпляры этих классов и произвольно использовать их методы и свойства протокола. Вот пример:

enum CellType {
    case a, b, c
   
    func getCellClass() -> ProtocolWithAssociatedType.Type { //Error here
        switch self {
        case .a: return ClassA.self
        case .b: return ClassB.self
        case .c: return ClassC.self
    }
}

Это перечисление вызывает ошибку Protocol 'CreateOrderTableViewCellProtocol' can only be used as a generic constraint because it has Self or associated type requirements в этой строке.

Так что это точный протокол, который у меня есть, за исключением названия:

protocol ProtocolWithAssociatedType: UITableViewCell {
    associatedtype Delegate
    var delegate: Delegate? {get set}
    func setupCell()
}

Все классы ClassA, ClassB и ClassC соответствуют этому. У всех есть свои собственные протоколы делегатов, которые они передают с помощью typealias, например:

protocol ClassADelegate: class {
...
}

class ClassA: UITableViewCell, ProtocolWithAssociatedType {
    typealias Delegate = ClassADelegate
    weak var delegate: ClassADelegate?
    func setupCell() {}

    ...
}

extension ViewController: ClassADelegate {
...
}

Все это делается для того, чтобы уменьшить tableView (... cellForItemAt: ...) и другие подобные методы, поскольку в этом проекте много классов ячеек, и он выходит за рамки читабельности, и действительно очень сложно делать какие-либо разработки на именно этот контроллер представления из-за этого. У меня есть расширение для UITableView для создания многоразовых ячеек для тех, у которых многоразовый идентификатор совпадает с именем его класса, например:

func dequeueReusableCell<C>(_ cellType: C.Type, for indexPath: IndexPath) -> C where C: UITableViewCell {
    let identifier = String(describing: cellType)
        
    guard let cell = dequeueReusableCell(withIdentifier: identifier, for: indexPath) as? C else {
        fatalError("Failed to find cell with identifier \"\(identifier)\" of type \"\(C.self)\"")
    }
        return cell
}

Итак, я готов использовать это следующим образом:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cellType = CellDataArray[indexPath.row].cellType
    let cellClass = cellType.getCellClass()

    let cell = tableView.dequeueReusableCell(cellClass, for: indexPath) \\ Here I have to have the cell conform to the protocol somehow

    cell.delegate = self \\So here is the associated type which doesn't need to be read but is need to be written only
    cell.setupCell() \\Obviously there are going to be some data passed to the cell instance right here
    return cell
}

Я прочитал так много вопросов и ответов, и я пытаюсь выполнить каждый из них, но у меня не получилось. Я стараюсь избегать массивных функций в контроллере представления и делать вещи максимально изменяемыми. Все эти ячейки сами действуют как маленькие контроллеры представления, и все они должны взаимодействовать с контроллером представления. Также ячейки не статичны, например, в разных случаях мне нужно показать разные ячейки. Прямо сейчас даже добавить одну простую ячейку - это адская работа, не говоря уже о создании совершенно нового случая. Но при таком подходе я пытаюсь сделать вещи ... модульными.

Итак, есть ли способ сделать это без сбоев во время выполнения здесь и там или создания черной дыры и завершения вселенной?

РЕДАКТИРОВАТЬ: Я пробовал использовать общий тип, но не смог. Из-за того, как я хочу, чтобы func getCellClass() работал, невозможно сделать так, чтобы компилятор знал, каким будет этот универсальный тип. Как следующее:

func getCellClass<C>() -> C.Type where C: ProtocolWithAssociatedValue {
...
}

Так что, даже если я принудительно передаю возвращаемые значения в C.Type, у меня возникает проблема, когда я вызываю это просто потому, что C неизвестен.

ИЗМЕНИТЬ

Я удалил связанное значение из протокола и сделал следующее:

protocol ProtocolWithAssociatedType: UITableViewCell {
    var _delegate: AnyObject? {get set}
    func setupCell()
}
protocol ClassADelegate: class {
...
}

class ClassA: UITableViewCell, ProtocolWithAssociatedType {
    weak var _delegate: AnyObject? { didSet { delegate = _delegate as? ClassADelegate } }
    private weak var delegate: ClassADelegate?
    func setupCell() {}

    ...
}

extension ViewController: ClassADelegate {
...
}

Таким образом, протокол можно использовать в качестве возвращаемого типа этой функции перечисления, и я могу принудительно привести к нему повторно используемую ячейку, поскольку сам протокол соответствует UITableViewCell. Я построил без каких-либо проблем, но пока не могу протестировать (тестовые серверы не работают в нерабочее время). Когда я тестирую его и если он работает без проблем, я отправлю его как решение.

0
Ömer Faruk Gökce 31 Окт 2020 в 18:03

2 ответа

Лучший ответ

РЕШЕНИЕ

Итак, после пары дней работы и пробной ошибки я наконец нашел решение. Благодаря @Cristik я понял и изменил свой подход.

Во-первых, я изменил способ обращения с делегатами. У меня был свой делегат на каждую камеру. Теперь у меня есть только один, который используют все эти клетки.

Таким образом, я смог сделать свою собственность следующим образом:

protocol CellProtocol: UITableViewCell {
    var delegate: CommonCellDelegate?
    func setupCell(...)
}

Для всех свойств и некоторых общих методов контроллера представления, который будут использовать ячейки, я объявил протокол делегата следующим образом:

protocol CommonCellDelegate: class {
    var viewControllerProperty1: SomeClass1 { get set }
    var viewControllerProperty2: SomeClass2 { get set }
    ...
    var viewControllerPropertyN: SomeClassN { get set }

    func someCommonFunction1(...) -> ...
    func someCommonFunction2(...) -> ...
    ...
    func someCommonFunctionN(...) -> ...
}

Поскольку мое намерение с самого начала состояло в том, чтобы контроллер представления оставался красивым и чистым, я поместил те необычные методы, которые нужны ячейкам индивидуально, под объявлениями классов этих ячеек в качестве расширения к свойству делегата:

class SomeCellClass: CellProtocol {
    var delegate: CommonCellDelegate?
    func setupCell(...) {
    ...
    }

    ...
}
extension CommonCellDelegate {
    func someMethodForTheCellAbove1(...) -> ... {
    ...
    }
    func someMethodForTheCellAbove2(...) -> ... {
    ...
    }
    .
    .
    .
}

Поскольку мне больше не нужен метод enum для возврата разных классов, я немного изменил его, чтобы возвращать идентификатор повторного использования каждого типа ячейки. Так что возвращать тип класса там не нужно:

enum CellType {
    case cellA, cellB, cellC, ...
   
    func getCellClass() -> String { //Error here
        switch self {
        case .cellA: return String(describing: ClassA.self)
        case .cellB: return String(describing: ClassB.self)
        case .cellC: return String(describing: ClassC.self)
        ...
        // Note: We use the the name of the cell classes as their reuse id's for simplicity.
    }
}

И, наконец, я смог довольно легко создать эти ячейки в методе tableView(_:cellForRowAt:), не создавая разрыва в ткани пространства-времени:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cellType = CellDataArray[indexPath.row].cellType
    let cellReuseId = String(describing: cellType.getCellId())

    guard let cell = tableView.dequeueReusableCell(withIdentifier: cellReuseId, for: indexPath) as? CellProtocol else { fatalError("some fatal error") }

    cell.delegate = self
    cell.setupCell(...)
    return cell
}

Таким образом, создавать новые реализации очень легко, так как все, что нужно сделать, это создать класс, привести его в соответствие с CellProtocol, указать его в перечислении, добавить регистр в массив где и когда это необходимо, зарегистрируйте идентификатор ячейки и вуаля! Он там! И поскольку все необходимые свойства теперь известны ячейке благодаря делегату, они могут легко использовать их как свои собственные свойства, с той лишь разницей, что это не self.something, а delegate?.something.

Я надеюсь, что мои усилия будут полезны другим. Ура!

0
Ömer Faruk Gökce 2 Ноя 2020 в 19:22

Похоже, вы пытаетесь смешать полиморфное поведение (то есть массив типов ячеек) со статическим поведением (протоколы со связанными типами), и это более или менее невозможно с текущим дизайном Swift.

Протоколы со связанными типами определяются во время компиляции, в то время как код в вашем методе tableView(_:cellForRowAt:) выполняется во время выполнения, что делает его несколько несовместимым с протоколами со связанными типами.

let cell = tableView.dequeueReusableCell(cellClass, for: indexPath)

В приведенном выше коде вам необходимо указать компилятору, какой тип вы ожидаете от ячейки, чтобы внедрить делегат, однако это функция времени компиляции, и она вам нужна во время выполнения, отсюда и несовместимость.

Я боюсь, что для этого вам придется придерживаться распространения / обработки ошибок во время выполнения. Если вы не хотите изменить delegate, чтобы он соответствовал всему набору возможных делегатов, и в этом случае связанный тип больше не нужен, что позволяет использовать функции времени выполнения.

protocol MyCell: UITableViewCell {
    var delegate: CellDelegate1 & CellDelegate2 ... & CellDelegateN { get set }

    func setupCell(SomeCommonProtocolThatGetsCastedAtRuntime)
}
0
Cristik 31 Окт 2020 в 15:32