Swift中URLSession代理设置优化及API返回数据获取疑问
嘿,我来帮你搞定这两个iOS网络请求里的常见问题——内存泄漏风险和代理方法中获取返回数据的事儿!
一、解决代理初始化导致的内存泄漏问题
你现在的写法确实有两个大问题:一是创建了一个全新的CreateAccountViewController实例作为代理,这个实例既没有加入视图层级,也没有被其他对象合理持有,但URLSession会强引用它的代理,导致这个多余的VC实例无法被释放,进而引发内存泄漏;二是这个新建的VC根本不是你当前正在显示的那个,它的回调对你的业务逻辑完全没用!
优化方案
我们需要让当前的CreateAccountViewController实例作为代理,同时避免循环引用:
- 修改
APICalls类,让它接受代理参数
不要在API工具类里自己创建VC实例,而是让外部传入符合协议的代理对象:
class APICalls { static func performLoginRequest(with request: URLRequest, delegate: URLSessionDataDelegate) { // 创建临时URLSession,不需要持有它——dataTask会持有session直到任务完成 let session = URLSession(configuration: .default, delegate: delegate, delegateQueue: nil) session.dataTask(with: request).resume() } }
- 让当前VC遵守协议并传递自身作为代理
在CreateAccountViewController中声明遵守相关协议,调用API时传入self:
class CreateAccountViewController: UIViewController, URLSessionDataDelegate, URLSessionTaskDelegate { // 点击登录按钮的方法 @IBAction func loginButtonTapped(_ sender: UIButton) { // 构造你的loginRequest... APICalls.performLoginRequest(with: loginRequest, delegate: self) } // 后续实现代理方法... }
这样既不会创建多余的VC实例,也能保证回调正确触发,从根源上避免了不必要的内存泄漏。
二、在代理方法中获取API返回的数据
你只实现了任务完成的代理方法,但要拿到返回数据,还需要处理数据接收的过程——因为网络数据可能分块返回,我们需要自己拼接完整数据:
- 在VC中声明变量存储接收的数据
class CreateAccountViewController: UIViewController, URLSessionDataDelegate, URLSessionTaskDelegate { private var receivedData = Data() // 用来拼接分块返回的数据 // ...其他代码... }
- 实现数据接收的代理方法
这个方法会在每次收到数据块时触发,我们把数据拼接到receivedData中:
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) { receivedData.append(data) }
- 在任务完成方法中处理完整数据
现在receivedData就是API返回的完整数据了,可以进行解析:
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { // 处理完数据后清空,避免干扰下次请求 defer { receivedData.removeAll() } if let error = error { print("登录请求失败:\(error.localizedDescription)") // 这里可以更新UI提示用户失败,注意要切到主线程 DispatchQueue.main.async { self.showErrorToast(message: "登录失败,请检查网络") } return } // 解析数据(假设返回的是JSON格式) do { let loginResponse = try JSONDecoder().decode(LoginResponse.self, from: receivedData) // 登录成功,处理业务逻辑(比如跳转首页、保存token) DispatchQueue.main.async { self.navigateToHomePage() } } catch { print("数据解析失败:\(error.localizedDescription)") DispatchQueue.main.async { self.showErrorToast(message: "数据解析错误") } } }
额外推荐:更简洁的闭包回调方式
如果没有特殊需求(比如监听上传/下载进度、自定义证书验证),推荐使用URLSession的闭包回调方式,不用处理代理,代码更紧凑,还能轻松避免循环引用:
// 在APICalls中实现闭包版本的登录方法 class APICalls { static func login(with request: URLRequest, completion: @escaping (Result<Data, Error>) -> Void) { URLSession.shared.dataTask(with: request) { data, _, error in if let error = error { completion(.failure(error)) return } guard let data = data else { let noDataError = NSError(domain: "LoginError", code: -1, userInfo: [NSLocalizedDescriptionKey: "未获取到返回数据"]) completion(.failure(noDataError)) return } completion(.success(data)) }.resume() } } // 在VC中调用 @IBAction func loginButtonTapped(_ sender: UIButton) { APICalls.login(with: loginRequest) { [weak self] result in guard let self = self else { return } // 避免循环引用 DispatchQueue.main.async { switch result { case .success(let data): do { let loginResponse = try JSONDecoder().decode(LoginResponse.self, from: data) self.navigateToHomePage() } catch { self.showErrorToast(message: "数据解析错误") } case .failure(let error): self.showErrorToast(message: error.localizedDescription) } } } }
这种方式不用管理代理和数据拼接,代码更易读,内存安全也更有保障。
内容的提问来源于stack exchange,提问作者ATCraiger
相关产品推荐
相关产品推荐

