передача данных с использованием универсального протокола

#swift

#быстрая

Вопрос:

У меня есть эта общая функция, которая запускает контроллер представления на основе переданных общих данных

 func processListData<T: FormDataSource>(title: String, data: [T]) {
    let vc = ListViewController(data: data)
    vc.coordinator = self
    push(vc, title: title.localized())
}
  

На сайте вызывающего абонента, если бы я использовал этот код

 func handleTableSelection(cellData: (String, [FormDataSource])){
    let (title,dataSource) = cellData
    coordinator?.processListData(title: title, data: dataSource)
}
  

отображается следующая ошибка

Значение типа протокола ‘FormDataSource’ не может соответствовать ‘FormDataSource’; только типы struct / enum / class могут соответствовать протоколам

итак, мне пришлось привести переменную FormDataSource, чтобы иметь возможность вызывать processListData следующим образом

 func handleTableSelection(cellData: (String, [FormDataSource])){
        
        let (title,dataSource) = cellData
        
        if let assumptions = dataSource as? [Assumption] {
            coordinator?.processListData(title: title, data: assumptions)
        } else if let stakeholders = dataSource as? [StakeHolder] {
            coordinator?.processListData(title: title, data: stakeholders)
        }
        //.... tons of else if 
  

Есть ли какой-нибудь лучший способ сохранить его универсальным и передать источник данных без приведения всех возможных моделей?

Комментарии:

1. Вы пытались ограничить FormDataSource протокол только классом? Что-то вроде protocol FormDataSource: class { } .

2. Можете ли вы сделать handleTableSelection универсальный? func handleTableSelection<U: FormDataSource>(cellData: (String, [U])) {

3. @ben да, я пробовал это, но он показывает ту же ошибку

4. @vadian выполнение этого переместит проблему в таблицу вызывающего (TableView:didSelectRowAt)

Ответ №1:

Если processListData имеется общая переменная T , то handleTableSelection она также должна иметь ту же форму, которую вы можете удалить <T: FormDataSource> в A, например:

 func processListData(title: String, data: [FormDataSource]) {
    let vc = ListViewController(data: data)
    vc.coordinator = self
    push(vc, title: title.localized())
}
  

Комментарии:

1. @DotFreelancer FormDataSource — это протокол, а не тип. Если вы создадите массив элементов, соответствующих протоколу, вы не сможете получить доступ к свойствам соответствующего ему типа, который не требуется протоколом.

2. @LeoDabus Спасибо, что упомянули об этом. В моем случае это нормально; я использую только свойство, объявленное в протоколе FormDataSource.

Ответ №2:

Вводя общие термины, вы должны предоставить компилятору возможность статически выводить типы из использования.

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

 enum DataSourceStorage {
    case assumption([Assumption])
    case stakeholder([StakeHolder])

    func process(with coordinator: Coordinator?, title: String) {
        switch self {
          case .assumption(let assumptions):
            coordinator?.processListData(title: title, data: assumptions)
          case .stakeholder(let stakeholders):
            coordinator?.processListData(title: title, data: stakeholders)
        }
    }
}
  

и теперь это можно обрабатывать напрямую

 func handleTableSelection(cellData: (String, DataSourceStorage)){
    let (title,dataSource) = cellData
    dataSource.process(with: coordinator, title: title)
}
  

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

 handleTableSelection(cellData: (title, .assumption(data))