iOS Swift 网络通信开发指南

1、 网络库选择

1.1、URLSession

URLSession 是 Apple 官方提供的网络框架,用于在 iOS、macOS、watchOS 和 tvOS 中进行所有类型的网络数据传输。

主要优势:
1、零依赖 - 原生集成,不增加安装包体积
2、最佳性能 - Apple 原生优化,官方维护
3、完全控制 - 可深度定制所有细节
4、现代化支持 - 完整的 async/await/Combine 支持

缺点:
1、API 较为底层,缺少不少开箱即用的高级功能,如拦截器、重试等(不过官方也在逐步完善)

1.2、Alamofire

Alamofire 是由 Alamofire Software Foundation 开源组织开发和维护的 Swift 网络库。Alamofire 不是要替代 Apple 的 URLSession,而是在其基础上构建了一个更高级的抽象层。它遵循"链式调用"和"约定优于配置"的设计理念,让网络编程变得更加简单直观。

主要优势:
1、链式语法设计 - 方法调用连贯、代码简洁、逻辑清晰
2、功能丰富全面 - 内置认证、重试、缓存、监控等
3、现代化支持 - 完整的 async/await/Combine 支持
4、生态完善 - 丰富的插件和扩展

缺点:
1、增加约 5-10MB 的安装包体积
2、性能稍差:内存占用高于直接使用 URLSession、初始化时间比原生方案长
3、依赖外部团队的维护

1.3、Moya

Moya 是一个基于 Alamofire 构建的网络抽象层,它通过在 Alamofire 之上添加类型安全的封装,为 iOS 应用提供更加结构化和类型安全的网络编程方案。

主要优势:
1、清晰的架构分层
2、测试友好性
3、极致的类型安全
4、插件化系统

缺点:
1、安装包体积增加
2、学习难度大
3、过度工程化风险(对于小型项目可能显得过于复杂)
4、灵活性受限
5、多重依赖(基于Alamofire -> 基于URLSession)

4、AFNetworking

AFNetworking 是一个用 Objective-C 编写的功能强大的网络库,在 Swift 语言出现之前,它曾是 iOS 开发中最主流、最广泛使用的网络库,为早期 iOS 应用开发提供了完整的网络解决方案。

主要优势:
1、稳定性高 - 多年生产环境验证
2、功能全面 - 覆盖所有常见网络需求
3、Objective-C 友好 - 混合开发最佳选择

缺点:
1、Swift 支持差 - 语法不优雅,缺少现代 Swift 特性支持
2、维护模式 - 新功能开发基本停止,主要进行维护性更新
3、架构过时 - 设计理念相对陈旧,缺少现代库的链式调用等特性

网络库方案选择建议:
现阶段,在常规项目需求下,URLSession 与 Alamofire 均为可行的网络库选择。若安装包体积为首要考量因素,则更推荐使用系统原生的 URLSession。对于 Swift 与 Objective-C 的混合项目,虽可沿用 AFNetworking,但建议在后续迭代中将其升级为更现代的网络库。

许多iOS开发者会思考的一个问题,也是非常具有前瞻性的问题:“未来,Alamofire是否会被废弃?

随着 URLSession 功能的持续完善,我认为未来 Alamofire 并不会被完全取代,而是其“不可替代性”将逐渐减弱。届时,开发者选择 Alamofire 将更多是基于对编码体验与特定高阶功能的偏好,而不再是因为 URLSession 无法实现相应能力。不知道大家如何看待这一趋势?欢迎在评论区分享您的见解。

2、URLSession介绍

2.1 URLSession 核心架构结构图

在这里插入图片描述
URLSession 框架采用了一种解耦且职责分明的架构,理解它们各自的角色与交互,是构建健壮、高效网络层的关键。

2.2、URLSessionConfiguration 会话配置

在 Swift 中,URLSessionConfiguration 是用于配置 URLSession 行为的重要类。它定义了会话的各种属性,如超时时间、缓存策略、Cookie 策略等。

提供了3种配置类型:

// 默认配置 - 启用共享的磁盘持久化缓存、Cookie 和凭证存储
let defaultConfig = URLSessionConfiguration.default

// 临时配置 - 不存储缓存、Cookie 到磁盘(有点类似于浏览器的无痕模式效果)
let ephemeralConfig = URLSessionConfiguration.ephemeral

// 后台配置 - 支持后台传输
let backgroundConfig = URLSessionConfiguration.background(
    withIdentifier: "com.yourapp.background"
)

常见的配置选项:

let config = URLSessionConfiguration.default

//1、超时配置
//请求超时时间
config.timeoutIntervalForRequest = 30.0
//资源超时时间
config.timeoutIntervalForResource = 60.0 * 60.0 // 1小时

//2、缓存策略
//缓存配置
config.urlCache = URLCache(
    memoryCapacity: 50 * 1024 * 1024,  // 50MB 内存缓存
    diskCapacity: 100 * 1024 * 1024,   // 100MB 磁盘缓存
    diskPath: nil
)
//请求缓存策略
config.requestCachePolicy = .useProtocolCachePolicy

//3、HTTP 头部设置
//设置公共请求头
config.httpAdditionalHeaders = [
    "User-Agent": "MyApp/1.0",
    "Accept": "application/json",
    "Accept-Language": "zh-CN"
]
//设置特定的 HTTP 头字段
config.httpAdditionalHeaders?["Authorization"] = "Bearer token123"

//4、Cookie自动管理
// Cookie 接受策略
config.httpCookieAcceptPolicy = .always
// Cookie 存储
config.httpCookieStorage = HTTPCookieStorage.shared

//5、网络配置
// 是否允许使用蜂窝网络
config.allowsCellularAccess = true
// 是否等待网络连接(在无网络时排队请求)
config.waitsForConnectivity = true

//6、连接池配置
config.httpMaximumConnectionsPerHost = 4 // 连接池限制
...
2.3、URLSession:会话

是核心入口点和管理者。它根据 Configuration来创建和管理具体的网络任务(Task)。

核心职责
作为工厂类,创建各种类型的 URLSessionTask。
持有并管理其创建的的所有 Task 的生命周期。
持有对 Delegate 的强引用,确保在任务执行期间回调能够被正确接收。
管理底层网络连接池,优化性能。

URLSession创建方式:

  1. 共享会话(Singleton)
// 直接使用单例
let session = URLSession.shared

// 等同于
// let session = URLSession(configuration: .default)
  1. 自定义会话
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config,
                       delegate: self, // 自定义委托对象
                       delegateQueue: .main) // 回调执行的队列
//在不需要时,须调用 session.finishTasksAndInvalidate() 或 session.invalidateAndCancel() 来打破循环引用并释放资源
2.3、URLSessionTask 任务

任务是具体的工作单元,它代表一个具体的网络请求操作。
我们可通过调用 Session 的方法来创建 Task,并通过操作 Task 来启动、暂停或取消网络请求。

核心职责
封装一个网络请求的完整生命周期(创建、执行、完成/错误、取消)。
跟踪任务状态(如暂停、运行、完成)。
提供进度查询(如已发送/接收的字节数)。

任务类型介绍:

  1. Data Task(数据任务)

    用途:最常用的任务类型,用于获取或发送小量数据
    特点:数据完全加载到内存中返回
    适用场景:API 调用、JSON/XML 数据交换、小文件传输
    内存考虑:不适合大文件,因为所有数据会一次性加载到内存

  2. Download Task(下载任务)

    用途:专门用于下载文件
    特点:数据直接写入临时文件,不占用内存
    优势:支持大文件下载、支持后台下载(即使应用被挂起)、支持断点续传
    输出:返回文件在磁盘上的临时位置 URL

  3. Upload Task(上传任务)

    用途:专门用于上传文件或数据
    特点:支持大文件上传、提供上传进度跟踪、支持后台上传
    数据源:可以从文件、Data 对象或流式数据上传

  4. Stream Task(流任务)

    用途:建立持久的 TCP/IP 连接
    特点:双向数据流通信/适合实时通信场景/保持长连接
    适用场景:自定义协议、实时消息传递、网络游戏

  5. WebSocket Task(WebSocket 任务)

    用途:WebSocket 协议通信
    特点:全双工通信通道/基于 HTTP/1.1 升级机制/适合实时双向数据交换
    适用场景:聊天应用、实时数据推送、协同编辑

2.4、URLSessionDelegate 委托

URLSessionDelegate 是一组协议,用于处理 URLSession 的各种事件和回调。

主要委托协议:

  1. URLSessionDelegate

    最基本的协议,处理会话级别的事件:
    会话生命周期:处理会话失效、完成事件
    认证挑战:处理服务器信任认证(SSL/TLS)
    后台传输完成:处理后台会话的任务完成通知

  2. URLSessionTaskDelegate

    继承自 URLSessionDelegate,处理任务级别的事件:
    重定向处理:控制 HTTP 重定向行为
    任务级认证:处理需要身份验证的请求
    上传进度:跟踪数据上传进度
    指标收集:获取网络连接指标(如延迟、吞吐量)

  3. URLSessionDataDelegate

    继承自 URLSessionTaskDelegate,专门处理数据任务:
    数据接收:分批接收响应数据
    数据缓存:控制响应缓存行为
    转为下载:将数据任务转换为下载任务

  4. URLSessionDownloadDelegate

    继承自 URLSessionTaskDelegate,专门处理下载任务:
    下载完成:处理下载文件的位置
    下载进度:跟踪下载进度
    恢复下载:处理断点续传

  5. URLSessionStreamDelegate

    继承自 URLSessionTaskDelegate,处理流任务:
    流建立:处理流连接建立事件
    流事件:读取和写入流的就绪事件

3、URLSession常用功能参考

3.1、GET 请求
// 最简单的 GET 请求
func getRequest(url: String) async throws -> Data {
    let (data, response) = try await URLSession.shared.data(from: URL(string: url)!)
    
    guard (response as? HTTPURLResponse)?.statusCode == 200 else {
        throw URLError(.badServerResponse)
    }
    
    return data
}
// 调用
Task {
    do {
        let data = try await getRequest(url: "https://api.example.com/data")
        print("成功: \(data)")
    } catch {
        print("失败: \(error)")
    }
}
3.2、POST 请求
// 简单的 POST 请求
func postRequest(url: String, json: [String: Any]) async throws -> Data {
    var request = URLRequest(url: URL(string: url)!)
    request.httpMethod = "POST"
    request.httpBody = try JSONSerialization.data(withJSONObject: json)
    request.setValue("application/json", forHTTPHeaderField: "Content-Type")
    
    let (data, response) = try await URLSession.shared.data(for: request)
    
    guard (response as? HTTPURLResponse)?.statusCode == 200 else {
        throw URLError(.badServerResponse)
    }
    
    return data
}
// 调用
Task {
    let result = try await postRequest(url: "https://api.example.com/users", 
                                     json: ["name": "John", "age": 25])
    print("提交成功")
}
3.3、文件上传
// 文件上传
func uploadFile(data: Data, to url: String) async throws -> Data {
    var request = URLRequest(url: URL(string: url)!)
    request.httpMethod = "POST"
    
    let (responseData, response) = try await URLSession.shared.upload(for: request, from: data)
    
    guard (response as? HTTPURLResponse)?.statusCode == 200 else {
        throw URLError(.badServerResponse)
    }
    
    return responseData
}
// 调用
Task {
    if let imageData = UIImage(systemName: "photo")?.jpegData(compressionQuality: 0.8) {
        let result = try await uploadFile(data: imageData, to: "https://api.example.com/upload")
        print("上传成功")
    }
}
3.4、带进度的文件上传
// 带 UI 进度显示的上传
func uploadWithUIProgress(
    fileData: Data,
    to url: String,
    progressHandler: @escaping (Float) -> Void
) async throws -> Data {
    
    var request = URLRequest(url: URL(string: url)!)
    request.httpMethod = "POST"
    
    let (data, response) = try await URLSession.shared.upload(for: request, from: fileData) { progress in
        // 在主线程更新 UI
        DispatchQueue.main.async {
            progressHandler(Float(progress.fractionCompleted))
        }
    }
    
    guard (response as? HTTPURLResponse)?.statusCode == 200 else {
        throw URLError(.badServerResponse)
    }
    
    return data
}
// 在 ViewController 中调用
class ViewController: UIViewController {
    @IBOutlet weak var progressView: UIProgressView!
    
    func startUpload() {
        Task {
            guard let fileData = getFileData() else { return }
            
            do {
                let result = try await uploadWithUIProgress(
                    fileData: fileData,
                    to: "https://api.example.com/upload"
                ) { [weak self] progress in
                    self?.progressView.progress = progress
                    print("进度: \(Int(progress * 100))%")
                }
                print("上传完成")
            } catch {
                print("上传失败: \(error)")
            }
        }
    }
    
    func getFileData() -> Data? {
        return UIImage(systemName: "photo")?.jpegData(compressionQuality: 0.8)
    }
}
3.5、多部分上传
// 多部分文件上传
func multipartUpload(
    url: String,
    fileData: Data,
    fileName: String,
    fieldName: String = "file",
    parameters: [String: String] = [:]
) async throws -> Data {
    
    let boundary = "Boundary-\(UUID().uuidString)"
    var request = URLRequest(url: URL(string: url)!)
    request.httpMethod = "POST"
    request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
    
    // 构建多部分数据
    var body = Data()
    
    // 添加文本参数
    for (key, value) in parameters {
        body.append("--\(boundary)\r\n".data(using: .utf8)!)
        body.append("Content-Disposition: form-data; name=\"\(key)\"\r\n\r\n".data(using: .utf8)!)
        body.append("\(value)\r\n".data(using: .utf8)!)
    }
    
    // 添加文件数据
    body.append("--\(boundary)\r\n".data(using: .utf8)!)
    body.append("Content-Disposition: form-data; name=\"\(fieldName)\"; filename=\"\(fileName)\"\r\n".data(using: .utf8)!)
    body.append("Content-Type: application/octet-stream\r\n\r\n".data(using: .utf8)!)
    body.append(fileData)
    body.append("\r\n".data(using: .utf8)!)
    
    // 结束标记
    body.append("--\(boundary)--\r\n".data(using: .utf8)!)
    
    request.httpBody = body
    
    let (data, response) = try await URLSession.shared.data(for: request)
    
    guard (response as? HTTPURLResponse)?.statusCode == 200 else {
        throw URLError(.badServerResponse)
    }
    
    return data
}
// 调用示例
Task {
    do {
        guard let image = UIImage(systemName: "photo"),
              let imageData = image.jpegData(compressionQuality: 0.8) else { return }
        
        let result = try await multipartUpload(
            url: "https://api.example.com/upload",
            fileData: imageData,
            fileName: "photo.jpg",
            parameters: ["title": "我的照片", "description": "上传测试"]
        )
        print("文件上传成功")
    } catch {
        print("上传失败: \(error)")
    }
}
3.6、文件下载
// 文件下载
func downloadFile(from url: String) async throws -> URL {
    let (localUrl, response) = try await URLSession.shared.download(from: URL(string: url)!)
    
    guard (response as? HTTPURLResponse)?.statusCode == 200 else {
        throw URLError(.badServerResponse)
    }
    
    return localUrl
}
// 调用
Task {
    let fileUrl = try await downloadFile(from: "https://example.com/file.pdf")
    print("下载完成: \(fileUrl)")
}
3.7、带进度的下载
// 带进度显示的下载
func downloadWithProgress(url: String) async throws -> URL {
    let (localUrl, response) = try await URLSession.shared.download(from: URL(string: url)!) { progress in
        print("下载进度: \(Int(progress.fractionCompleted * 100))%")
    }
    
    guard (response as? HTTPURLResponse)?.statusCode == 200 else {
        throw URLError(.badServerResponse)
    }
    
    return localUrl
}
3.8、并发请求
// 并发执行多个请求
func concurrentRequests() async {
    async let request1 = getRequest(url: "https://api.example.com/users")
    async let request2 = getRequest(url: "https://api.example.com/posts")
    async let request3 = getRequest(url: "https://api.example.com/comments")
    
    do {
        let (users, posts, comments) = try await (request1, request2, request3)
        print("所有请求完成")
    } catch {
        print("某个请求失败: \(error)")
    }
}
3.9、顺序请求
// 顺序请求 - 依赖前一个结果
func sequentialRequests() async throws {
    // 第一步:获取用户信息
    let userData = try await getUserInfo()
    
    // 第二步:使用用户ID获取帖子
    let postsData = try await getPosts(userId: extractUserId(from: userData))
    
    // 第三步:使用帖子ID获取评论
    let commentsData = try await getComments(postId: extractPostId(from: postsData))
    
    print("所有顺序请求完成")
}
3.10 Delegate拦截请求,自定义header
class SimpleHeaderDelegate: NSObject, URLSessionTaskDelegate {
    
    func urlSession(_ session: URLSession, 
                   task: URLSessionTask, 
                   willPerformHTTPRedirection response: HTTPURLResponse, 
                   newRequest request: URLRequest, 
                   completionHandler: @escaping (URLRequest?) -> Void) {
        
        var newRequest = request
        // 添加 Header
        newRequest.setValue("Bearer token123", forHTTPHeaderField: "Authorization")
        newRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
        
        completionHandler(newRequest)
    }
}
// 创建带拦截的 Session
let delegate = SimpleHeaderDelegate()
let session = URLSession(configuration: .default, delegate: delegate, delegateQueue: nil)

// 发起请求 - 会自动添加 Header
Task {
    let url = URL(string: "https://api.example.com/data")!
    let request = URLRequest(url: url)
    
    let (data, _) = try await session.data(for: request)
    print("请求完成")
}
3.11、Delegate拦截响应,判断权限
class SimpleResponseDelegate: NSObject, URLSessionDataDelegate {
    
    func urlSession(_ session: URLSession, 
                   dataTask: URLSessionDataTask, 
                   didReceive response: URLResponse,
                   completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) {
        
        // 检查是否无权限
        if let httpResponse = response as? HTTPURLResponse,
           httpResponse.statusCode == 401 {
            print("❌ 无权限访问")
            // 处理无权限逻辑
            handleUnauthorized()
        }
        
        completionHandler(.allow) // 允许继续请求
    }
    
    private func handleUnauthorized() {
        // 跳转到登录页面等操作
        print("需要重新登录")
    }
}
// 创建带响应拦截的 Session
let delegate = SimpleResponseDelegate()
let session = URLSession(configuration: .default, delegate: delegate, delegateQueue: nil)

// 发起请求
Task {
    let url = URL(string: "https://api.example.com/protected")!
    let request = URLRequest(url: url)
    
    do {
        let (data, _) = try await session.data(for: request)
        print("请求成功")
    } catch {
        print("请求失败: \(error)")
    }
}
Logo

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

更多推荐