Да, технически у вас может быть отдельный объект в качестве делегата для сеанса. Но создавать экземпляр контроллера представления для этого не имеет большого смысла по нескольким причинам:
Ваш код создает экземпляр контроллера представления в качестве объекта делегата, но вы передать это URLSession
без сохранения ссылки на него. Таким образом, нет способа добавить это в иерархию контроллеров представления (например, представить его, сделать к нему sh, выполнить переход к нему, что угодно).
Конечно, вы можете представить другой экземпляр этого контроллера представления в другом месте, но это будет совершенно отдельный экземпляр, не связанный с тем, который вы только что создали здесь. В итоге вы получите два отдельных объекта CreateAccountViewController
.
С архитектурной точки зрения многие будут утверждать, что код сетевого делегата в любом случае не принадлежит контроллерам представления. Контроллеры представлений предназначены для заполнения представлений и реагирования на пользовательские события, а не для сетевого кода.
Итак, вкратце, хотя технически вы можете использовать в своем классе диспетчера 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
}
}