Используйте общие протоколы в качестве типа переменной - PullRequest
1 голос
/ 19 апреля 2019

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

protocol FetchDataDelegate: AnyObject {

    associatedtype ResultData

    func didStartFetchingData()
    func didFinishFetchingData(with data: ResultData)
    func didFinishFetchingData(with error: Error)
}

class Controller: UIViewController, FetchDataDelegate {

    func didStartFetchingData() {
        //...
    }

    func didFinishFetchingData(with data: ResultData) {
        //...
    }

    func didFinishFetchingData(with error: Error) {
        //...
    }
}

class NetworkManager {

    weak var delegate: FetchDataDelegate?
    //...
}

Класс Controller будет иметь ссылку на экземпляр NetworkManager, и через это я хотел бы начать сетевые операции. Когда операция заканчивается, я хотел бы вызвать соответствующую функцию делегата, чтобы передать результат обратно в контроллер. Но с этой настройкой я получил следующую ошибку: Протокол 'FetchDataDelegate' может использоваться только в качестве общего ограничения, поскольку он имеет требования к Self или связанный тип Вопрос в том, что я должен сделать, чтобы использовать этот общий протокол как тип переменной? Или, если это невозможно, что будет правильным? Спасибо!

Ответы [ 2 ]

2 голосов
/ 19 апреля 2019

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

Во-первых, представьте, что может сохранить эту переменную. Что бы вы сделали с этим? Какая функция в NetworkManager может вызвать delegate.didFinishFetchingData? Как бы вы сгенерировали ResultData, если не знаете, что это такое?

Дело в том, что это не то, для чего предназначены PAT (протоколы со связанными типами). Это не их цель. Их цель - помочь вам добавить расширения к другим типам или ограничить типы типов, которые можно передавать в универсальные алгоритмы. Для этих целей они невероятно мощные. Но вам нужны дженерики, а не протоколы.

Вместо создания делегатов вы должны использовать универсальные функции для обработки результата конкретного вызова, а не пытаться привязать каждый контроллер представления к определенному типу результата (который в любом случае не очень гибок). Например, самым простым и наименее гибким способом, который все еще дает вам отчеты о проделанной работе:

struct APIClient {
    func fetch<Model: Decodable>(_: Model.Type,
                                 with urlRequest: URLRequest,
                                 completion: @escaping (Result<Model, Error>) -> Void)
    -> Progress {

        let session = URLSession.shared

        let task = session.dataTask(with: urlRequest) { (data, _, error) in
            if let error = error {
                completion(.failure(error))
            }
            else if let data = data {
                let decoder = JSONDecoder()
                completion(Result {
                    try decoder.decode(Model.self, from: data)
                })
            }
        }
        task.resume()
        return task.progress
    }
}

let progress = APIClient().fetch(User.self, with: urlRequest) { user in ... }

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

(Если ваш didStartFetchingData метод очень важен, способами, которые Progress не решают, оставьте комментарий, и я покажу, как реализовать такие вещи. Это не сложно, но этот ответ довольно уже давно.)

0 голосов
/ 19 апреля 2019

Возможный дубликат этого

Таким образом, вы не можете использовать универсальные протоколы в качестве типов переменных, их необходимо использовать как универсальное ограничение, поскольку вы не знаете тип ResultData во время компиляции

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...