Установить делегата URLSession для другого класса Swift - PullRequest
0 голосов
/ 30 мая 2020

Я пытаюсь вызвать API для входа на веб-сайт. В настоящее время у меня есть все вызовы API в быстром классе APICalls. Мой контроллер представления, который я использую для входа в систему, называется CreateAccountViewController.

В моем вызове API для входа в систему я создаю сеанс URL и устанавливаю делегата следующим образом:

let task = URLSession.init(configuration: URLSessionConfiguration.default, delegate: CreateAccountViewController.init(), delegateQueue: nil)

task.dataTask(with: request).resume()

Затем в моем классе V C у меня есть эта функция

func urlSession(_: URLSession, task: URLSessionTask, didCompleteWithError: Error?) {
        //Check the data returned from API call, ensure user is logged in
    }

Эта функция вызывается после завершения работы API, но я чувствую, что вызываю утечку памяти или что-то еще, используя .init в объявлении делегата при создании сеанса URL. Есть лучший способ сделать это?

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

Ответы [ 2 ]

1 голос
/ 30 мая 2020

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

  1. Ваш код создает экземпляр контроллера представления в качестве объекта делегата, но вы передать это URLSession без сохранения ссылки на него. Таким образом, нет способа добавить это в иерархию контроллеров представления (например, представить его, сделать к нему sh, выполнить переход к нему, что угодно).

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

  2. С архитектурной точки зрения многие будут утверждать, что код сетевого делегата в любом случае не принадлежит контроллерам представления. Контроллеры представлений предназначены для заполнения представлений и реагирования на пользовательские события, а не для сетевого кода.

Итак, вкратце, хотя технически вы можете использовать в своем классе диспетчера API отдельный объект для делегата звонки, это немного необычно. И если бы вы это сделали, вы бы точно не создавали для этого подкласс UIViewController.

Более распространенным шаблоном (если вы вообще используете шаблон делегата) могло бы быть создание самого диспетчера API , делегат для своего URLSession. (Добавление отдельного объекта делегата в микс, вероятно, только усложняет ситуацию.) Но, убирая весь этот сетевой код c вне контроллеров представления, вы абстрагируете свои контроллеры представления от кровавых деталей анализа сетевых ответов. , обрабатывая все различные методы делегата, et c.

Все это вызывает вопрос: действительно ли вам нужно использовать API на основе делегатов? Это критично в тех редких случаях, когда вам нужен богатый API-интерфейс делегата (обработка пользовательских ответов на запросы и т. Д. c.), Но в большинстве случаев представление dataTask простым обработчиком завершения намного проще.

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

Таким образом, общий шаблон будет должен предоставить вашему диспетчеру API (который, как я полагаю, является одноэлементным) метод login, например:

/// Perform login request
///
/// - Parameters:
///   - userid: Userid string.
///   - password: Password string
///   - completion: Calls with `.success(true)` if successful. Calls `.failure(Error)` on error.
///
/// - Returns: The `URLSessionTask` of the network request (in case caller wants to cancel request).

@discardableResult
func login(userid: String, password: String, completion: @escaping (Result<Bool, Error>) -> Void) -> URLSessionTask {
    let request = ... // Build your `URLRequest` here

    let task = URLSession.shared.dataTask(with: request) { data, response, error in
        guard
            error == nil,
            let responseData = data,
            let httpResponse = response as? HTTPURLResponse,
            200 ..< 300 ~= httpResponse.statusCode
        else {
            DispatchQueue.main.async { completion(.failure(error ?? APIManagerError.invalidResponse(data, response))) }
            return
        }

        // parse `responseData` here

        let success = true

        DispatchQueue.main.async {
            if success {
                completion(.success(true))
            } else {
                completion(.failure(error))
            }
        }
    }
    task.resume()
    return task
}

Где у вас может быть собственный класс ошибки, например:

enum APIManagerError: Error {
    case invalidResponse(Data?, URLResponse?)
    case loginFailed(String)
}

И вы бы назвали это так:

APIManager.shared.login(userid: userid, password: password) { result in
    switch result {
    case .failure(let error):
        // update UI to reflect error
        print(error)

    case .success:
        // do whatever you want if the login was successful
    }
}

Ниже приведен более полный пример, в котором я немного разбил сетевой код (один для выполнения сетевых запросов, один общий c метод для синтаксического анализа JSON, один конкретный c метод для синтаксического анализа JSON, связанный с входом в систему), но идея все та же. Когда вы выполняете асинхронный метод, дайте методу @escaping завершение обработчика завершения, которое вызывается, когда асинхронная задача выполнена.

final class APIManager {
    static let shared = APIManager()

    private var session: URLSession

    private init() {
        session = .shared
    }

    let baseURLString = "https://example.com"

    enum APIManagerError: Error {
        case invalidResponse(Data?, URLResponse?)
        case loginFailed(String)
    }

    /// Perform network request with `Data` response.
    ///
    /// - Parameters:
    ///   - request: The `URLRequest` to perform.
    ///   - completion: Calls with `.success(Data)` if successful. Calls `.failure(Error)` on error.
    ///
    /// - Returns: The `URLSessionTask` of the network request (in case caller wants to cancel request).

    @discardableResult
    func perform(_ request: URLRequest, completion: @escaping (Result<Data, Error>) -> Void) -> URLSessionTask {
        let task = session.dataTask(with: request) { data, response, error in
            guard
                error == nil,
                let responseData = data,
                let httpResponse = response as? HTTPURLResponse,
                200 ..< 300 ~= httpResponse.statusCode
            else {
                completion(.failure(error ?? APIManagerError.invalidResponse(data, response)))
                return
            }

            completion(.success(responseData))
        }

        task.resume()
        return task
    }

    /// Perform network request with JSON response.
    ///
    /// - Parameters:
    ///   - request: The `URLRequest` to perform.
    ///   - completion: Calls with `.success(Data)` if successful. Calls `.failure(Error)` on error.
    ///
    /// - Returns: The `URLSessionTask` of the network request (in case caller wants to cancel request).

    @discardableResult
    func performJSON<T: Decodable>(_ request: URLRequest, of type: T.Type, completion: @escaping (Result<T, Error>) -> Void) -> URLSessionTask {
        return perform(request) { result in
            switch result {
            case .failure(let error):
                completion(.failure(error))

            case .success(let data):
                do {
                    let responseObject = try JSONDecoder().decode(T.self, from: data)
                    completion(.success(responseObject))
                } catch let parseError {
                    completion(.failure(parseError))
                }
            }
        }
    }

    /// Perform login request
    ///
    /// - Parameters:
    ///   - userid: Userid string.
    ///   - password: Password string
    ///   - completion: Calls with `.success()` if successful. Calls `.failure(Error)` on error.
    ///
    /// - Returns: The `URLSessionTask` of the network request (in case caller wants to cancel request).

    @discardableResult
    func login(userid: String, password: String, completion: @escaping (Result<Bool, Error>) -> Void) -> URLSessionTask {
        struct ResponseObject: Decodable {
            let success: Bool
            let message: String?
        }

        let request = prepareLoginRequest(userid: userid, password: password)

        return performJSON(request, of: ResponseObject.self) { result in
            switch result {
            case .failure(let error):
                completion(.failure(error))

            case .success(let responseObject):
                if responseObject.success {
                    completion(.success(true))
                } else {
                    completion(.failure(APIManagerError.loginFailed(responseObject.message ?? "Unknown error")))
                }
                print(responseObject)
            }
        }
    }

    private func prepareLoginRequest(userid: String, password: String) -> URLRequest {
        var components = URLComponents(string: baseURLString)!
        components.query = "login"
        components.queryItems = [
            URLQueryItem(name: "userid", value: userid),
            URLQueryItem(name: "password", value: password)
        ]

        var request = URLRequest(url: components.url!)
        request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
        request.setValue("application/json", forHTTPHeaderField: "Accept")

        return request
    }
}
0 голосов
/ 30 мая 2020

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

А для данных вы можете использовать другой метод

func urlSession(_ session: URLSession, 
                dataTask: URLSessionDataTask, 
              didReceive data: Data)

Здесь вы получите данные

...