2、URLSession入门:URLSession配置、DataTask与DownloadTask、基本GET/POST请求实现

好,咱们正式开始网络层的实战部分。

说到iOS网络编程,绕不开的就是URLSession。说实话,这个类从iOS 7开始就成了苹果网络请求的官方标配。我当年刚接触时还觉得它有点啰嗦,配置项太多。但用久了你会发现——这种设计恰恰是为了灵活

2.1 URLSession的三种配置模式

先聊聊URLSessionConfiguration。我个人习惯把它理解成「网络请求的预设模板」。你创建Session时给它一个配置,后续所有任务都按这个规矩来。

苹果提供了三种配置:

配置类型 特点 适用场景
.default 使用全局缓存、Cookie、凭证策略 普通网络请求,90%的情况用这个
.ephemeral 不持久化任何数据,纯内存操作 隐私浏览、敏感数据请求
.background 支持后台传输,即使App被挂起 大文件下载、上传任务

重点提醒.background配置只能用于UploadTaskDownloadTask,不能用于DataTask。我刚开始踩过这个坑,调试了半天才发现。

创建Session的代码很简单:

let config = URLSessionConfiguration.default
config.timeoutIntervalForRequest = 30
config.waitsForConnectivity = true
let session = URLSession(configuration: config)

嗯,这里有个小细节——waitsForConnectivity。默认是false,如果网络不通直接失败。设为true后,Session会等网络恢复再重试。我在做离线模式时特别喜欢这个属性。

2.2 DataTask vs DownloadTask

这两个任务类型,说白了就是「内存里处理」和「磁盘上处理」的区别。

  • DataTask:数据直接加载到内存,适合小数据量(JSON、图片等)
  • DownloadTask:数据写入临时文件,适合大文件下载

为什么会这样设计?你想想看,如果下载一个500MB的视频,直接塞内存里,App不崩才怪。DownloadTask会把数据分块写入磁盘,内存占用极低。

我的经验:判断用哪个很简单——如果响应体超过10MB,我建议用DownloadTask。曾经有个项目,用DataTask下载高清图片,结果iPhone 6直接闪退。换成DownloadTask后,问题解决。

2.3 基本GET请求实现

GET请求是最基础的。咱们直接上代码:

let url = URL(string: "https://api.example.com/users")!
let task = URLSession.shared.dataTask(with: url) { data, response, error in
    guard let data = data, error == nil else {
        print("请求失败: \(error?.localizedDescription ?? "未知错误")")
        return
    }
    // 解析JSON
    if let json = try? JSONSerialization.jsonObject(with: data) {
        print("结果: \(json)")
    }
}
task.resume() // 别忘了这步!

注意task.resume()。新创建的Task默认是挂起状态,必须调用resume()才会开始。我见过不少新手在这卡住——明明代码写对了,就是不执行,其实就是忘了这行。

2.4 基本POST请求实现

POST请求需要设置请求体。我个人习惯用URLRequest来构建:

let url = URL(string: "https://api.example.com/login")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")

let body: [String: Any] = ["username": "admin", "password": "123456"]
request.httpBody = try? JSONSerialization.data(withJSONObject: body)

let task = URLSession.shared.dataTask(with: request) { data, response, error in
    // 处理响应
}
task.resume()

避坑指南:我曾经犯过一个低级错误——忘记设置Content-Type头。服务端返回415错误,我排查了半天才发现。记住,发送JSON就必须声明application/json

2.5 带进度的DownloadTask

下载大文件时,用户需要看到进度。DownloadTask配合URLSessionDownloadDelegate可以实现:

class DownloadManager: NSObject, URLSessionDownloadDelegate {
    lazy var session: URLSession = {
        let config = URLSessionConfiguration.default
        return URLSession(configuration: config, delegate: self, delegateQueue: nil)
    }()
    
    func downloadFile(from url: URL) {
        let task = session.downloadTask(with: url)
        task.resume()
    }
    
    // MARK: - Delegate
    func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask,
                    didWriteData bytesWritten: Int64,
                    totalBytesWritten: Int64,
                    totalBytesExpectedToWrite: Int64) {
        let progress = Float(totalBytesWritten) / Float(totalBytesExpectedToWrite)
        DispatchQueue.main.async {
            print("下载进度: \(progress * 100)%")
        }
    }
    
    func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask,
                    didFinishDownloadingTo location: URL) {
        // 下载完成,文件在临时目录
        let documentsPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
        let destinationURL = documentsPath.appendingPathComponent("file.pdf")
        try? FileManager.default.moveItem(at: location, to: destinationURL)
    }
}

这里有个关键点——下载完成的文件在临时目录,你必须立即把它移到持久化目录,否则系统随时可能清理掉。我刚开始没注意,结果下载完一刷新,文件没了。

2.6 小结

这一章咱们把URLSession的骨架搭起来了。记住几个核心点:

  • 配置决定Session的行为模式
  • DataTask适合小数据,DownloadTask适合大文件
  • 别忘了task.resume()Content-Type

下一章咱们会深入URLSessionDelegate,处理认证挑战和重定向。到时候你会发现,网络层的灵活性远比你想象的大。