将 URLSession 委托设置为另一个 Swift Class
Set URLSession Delegate To Another Swift Class
我正在尝试调用 API 来登录网站。我目前的所有 API 通话都在一个名为 API 的 swift class 通话中。我用来登录的视图控制器称为 CreateAccountViewController
。
在我的 API 登录调用中,我创建了一个 URL 会话并像这样设置委托:
let task = URLSession.init(configuration: URLSessionConfiguration.default, delegate: CreateAccountViewController.init(), delegateQueue: nil)
task.dataTask(with: request).resume()
然后在我的VCclass我有这个功能
func urlSession(_: URLSession, task: URLSessionTask, didCompleteWithError: Error?) {
//Check the data returned from API call, ensure user is logged in
}
此函数在 API 完成时被调用,但我觉得我在创建 URL 会话时在委托声明中使用 .init 导致内存泄漏或其他问题.有一个更好的方法吗?
此外,如何访问 API 调用中的数据?在完成处理程序中有一个我可以获得的数据响应,但在这个委托调用中没有。
最好不要通过在 URL 会话案例中委派其他 class 来完成此操作... return 完成处理程序中的数据并在您的 [=17= 中访问它]
对于数据你可以使用其他方法
func urlSession(_ session: URLSession,
dataTask: URLSessionDataTask,
didReceive data: Data)
在这里你会得到接收到的数据
是的,从技术上讲,您可以让一个单独的对象作为会话的委托。但是为此实例化视图控制器没有多大意义,原因如下:
您的代码正在创建一个视图控制器实例作为委托对象,但是您将其交给了 URLSession
而没有保留对它的引用。因此,没有办法将其添加到视图控制器层次结构中(例如,呈现它、推送它、对其执行 segue 等)。
当然,您可能会在其他地方展示此视图控制器的另一个实例,但那将是一个完全独立的实例,与您刚刚在此处创建的实例没有任何联系。您最终会得到两个单独的 CreateAccountViewController
对象。
从架构的角度来看,许多人会争辩说网络委托代码并不真正属于视图控制器。视图控制器用于填充视图和响应用户事件,而不是用于网络代码。
因此,简而言之,虽然您在技术上可以让您的 API 经理 class 使用单独的对象进行委托调用,但这有点不寻常。如果你这样做了,你肯定不会为此创建一个 UIViewController
subclass。
一个更常见的模式(如果您完全使用委托模式)可能是让 API 经理本身成为其 URLSession
的委托。 (在混合中添加一个单独的专用委托对象可能只会使情况复杂化。)但是通过将所有这些特定于网络的代码保留在视图控制器之外,您将视图控制器从解析网络响应的血淋淋的细节中抽象出来,处理所有各种委托方法等
所有这些都引出了一个问题:您真的需要使用基于委托的 API 吗?在极少数情况下,您需要丰富的委托 API(处理自定义质询响应等),但在大多数情况下,dataTask 的简单完成处理程序再现要容易得多。
为您的 API 方法提供一个完成处理程序闭包,以便调用者可以指定如果网络请求成功应该发生什么。您可以使用基于委托的会话来做到这一点,但它要复杂得多,我们通常只会在绝对必要时才去那个兔子洞,这里不是这种情况。
所以一个常见的模式是给你的 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
}
您可能会遇到自定义错误 class,如下所示:
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
}
}
下面是一个更完整的示例,其中我将网络代码分解了一点(一个用于执行网络请求,一个用于解析 JSON 的通用方法,一个用于解析 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
}
}
我正在尝试调用 API 来登录网站。我目前的所有 API 通话都在一个名为 API 的 swift class 通话中。我用来登录的视图控制器称为 CreateAccountViewController
。
在我的 API 登录调用中,我创建了一个 URL 会话并像这样设置委托:
let task = URLSession.init(configuration: URLSessionConfiguration.default, delegate: CreateAccountViewController.init(), delegateQueue: nil)
task.dataTask(with: request).resume()
然后在我的VCclass我有这个功能
func urlSession(_: URLSession, task: URLSessionTask, didCompleteWithError: Error?) {
//Check the data returned from API call, ensure user is logged in
}
此函数在 API 完成时被调用,但我觉得我在创建 URL 会话时在委托声明中使用 .init 导致内存泄漏或其他问题.有一个更好的方法吗?
此外,如何访问 API 调用中的数据?在完成处理程序中有一个我可以获得的数据响应,但在这个委托调用中没有。
最好不要通过在 URL 会话案例中委派其他 class 来完成此操作... return 完成处理程序中的数据并在您的 [=17= 中访问它]
对于数据你可以使用其他方法
func urlSession(_ session: URLSession,
dataTask: URLSessionDataTask,
didReceive data: Data)
在这里你会得到接收到的数据
是的,从技术上讲,您可以让一个单独的对象作为会话的委托。但是为此实例化视图控制器没有多大意义,原因如下:
您的代码正在创建一个视图控制器实例作为委托对象,但是您将其交给了
URLSession
而没有保留对它的引用。因此,没有办法将其添加到视图控制器层次结构中(例如,呈现它、推送它、对其执行 segue 等)。当然,您可能会在其他地方展示此视图控制器的另一个实例,但那将是一个完全独立的实例,与您刚刚在此处创建的实例没有任何联系。您最终会得到两个单独的
CreateAccountViewController
对象。从架构的角度来看,许多人会争辩说网络委托代码并不真正属于视图控制器。视图控制器用于填充视图和响应用户事件,而不是用于网络代码。
因此,简而言之,虽然您在技术上可以让您的 API 经理 class 使用单独的对象进行委托调用,但这有点不寻常。如果你这样做了,你肯定不会为此创建一个 UIViewController
subclass。
一个更常见的模式(如果您完全使用委托模式)可能是让 API 经理本身成为其 URLSession
的委托。 (在混合中添加一个单独的专用委托对象可能只会使情况复杂化。)但是通过将所有这些特定于网络的代码保留在视图控制器之外,您将视图控制器从解析网络响应的血淋淋的细节中抽象出来,处理所有各种委托方法等
所有这些都引出了一个问题:您真的需要使用基于委托的 API 吗?在极少数情况下,您需要丰富的委托 API(处理自定义质询响应等),但在大多数情况下,dataTask 的简单完成处理程序再现要容易得多。
为您的 API 方法提供一个完成处理程序闭包,以便调用者可以指定如果网络请求成功应该发生什么。您可以使用基于委托的会话来做到这一点,但它要复杂得多,我们通常只会在绝对必要时才去那个兔子洞,这里不是这种情况。
所以一个常见的模式是给你的 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
}
您可能会遇到自定义错误 class,如下所示:
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
}
}
下面是一个更完整的示例,其中我将网络代码分解了一点(一个用于执行网络请求,一个用于解析 JSON 的通用方法,一个用于解析 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
}
}