告别进度条卡顿:Alamofire上传下载进度监控全解析

告别进度条卡顿:Alamofire上传下载进度监控全解析

【免费下载链接】Alamofire Alamofire/Alamofire: Alamofire 是一个用于 iOS 和 macOS 的网络库,提供了 RESTful API 的封装和 SDK,可以用于构建网络应用程序和 Web 服务。 【免费下载链接】Alamofire 项目地址: https://gitcode.com/GitHub_Trending/al/Alamofire

你是否曾因无法准确掌握文件上传下载进度而困扰?用户等待时没有反馈,进度条停滞不前,最终导致应用体验下降?Alamofire作为iOS和macOS平台最流行的网络库,提供了强大的进度监控能力,却被许多开发者忽视。本文将从基础实现到高级优化,全面讲解如何利用Alamofire打造流畅的进度反馈系统,让用户随时掌握文件传输状态。

进度监控核心原理

Alamofire的进度跟踪基于URLSessionTask的进度回调机制,通过Progress对象实时反馈传输状态。所有上传下载请求都继承自Request类,该类提供了uploadProgressdownloadProgress两个关键属性,分别对应上传和下载进度。

public class Request {
    /// Progress object monitoring upload progress.
    public let uploadProgress: Progress
    /// Progress object monitoring download progress.
    public let downloadProgress: Progress
    
    /// Sets a closure to be called periodically during upload progress updates.
    @discardableResult
    public func uploadProgress(queue: DispatchQueue = .main, 
                              closure: @escaping (Progress) -> Void) -> Self
    
    /// Sets a closure to be called periodically during download progress updates.
    @discardableResult
    public func downloadProgress(queue: DispatchQueue = .main, 
                                closure: @escaping (Progress) -> Void) -> Self
}

源码位置:Source/Core/Request.swift

Progress对象包含三个核心属性:

  • completedUnitCount: 已完成的字节数
  • totalUnitCount: 总字节数
  • fractionCompleted: 完成比例(completedUnitCount/totalUnitCount

下载进度监控实现

下载进度监控需要服务器返回Content-Length响应头,Alamofire会自动解析该值并配置downloadProgress对象。基础实现只需链式调用downloadProgress方法:

基础用法

let destination: DownloadRequest.Destination = { tempURL, response in
    let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
    let fileURL = documentsURL.appendingPathComponent(response.suggestedFilename!)
    return (fileURL, [.removePreviousFile, .createIntermediateDirectories])
}

AF.download("https://example.com/large-file.zip", to: destination)
    .downloadProgress { progress in
        print("下载进度: \(progress.fractionCompleted * 100)%")
        // 更新UI进度条
        DispatchQueue.main.async {
            self.progressView.progress = Float(progress.fractionCompleted)
            self.statusLabel.text = String(format: "已下载: %.2fMB/%.2fMB",
                                          Double(progress.completedUnitCount)/1024/1024,
                                          Double(progress.totalUnitCount)/1024/1024)
        }
    }
    .response { response in
        if response.error == nil, let fileURL = response.fileURL {
            print("文件保存路径: \(fileURL.path)")
        }
    }

高级配置

对于大文件下载,建议添加暂停/继续功能,Alamofire的DownloadRequest支持通过resumeData恢复下载:

var resumeData: Data?
var downloadRequest: DownloadRequest?

// 暂停下载
@IBAction func pauseDownload(_ sender: UIButton) {
    downloadRequest?.cancel(byProducingResumeData: { data in
        self.resumeData = data
        self.downloadRequest = nil
    })
}

// 继续下载
@IBAction func resumeDownload(_ sender: UIButton) {
    guard let resumeData = resumeData else { return }
    
    downloadRequest = AF.download(resumingWith: resumeData)
        .downloadProgress { progress in
            // 更新进度UI
        }
        .response { response in
            // 处理完成
        }
}

官方文档:Documentation/Usage.md#download-progress

上传进度监控实现

上传进度监控支持三种数据源类型:Data、文件URL和InputStream。Alamofire会根据数据源自动计算总大小并跟踪上传进度。

不同数据源的进度监控

1. Data上传
let imageData = UIImage.pngData(UIImage(named: "large-image")!)!

AF.upload(imageData, to: "https://example.com/upload")
    .uploadProgress { progress in
        print("上传进度: \(progress.fractionCompleted * 100)%")
    }
    .responseJSON { response in
        // 处理响应
    }
2. 文件上传
let fileURL = Bundle.main.url(forResource: "large-file", withExtension: "zip")!

AF.upload(fileURL, to: "https://example.com/upload")
    .uploadProgress { progress in
        // 更新进度UI
    }
    .response { response in
        // 处理响应
    }
3. 多表单上传
AF.upload(multipartFormData: { multipartFormData in
    multipartFormData.append(imageData, withName: "avatar", fileName: "avatar.png", mimeType: "image/png")
    multipartFormData.append("user123".data(using: .utf8)!, withName: "user_id")
}, to: "https://example.com/upload")
.uploadProgress { progress in
    // 总上传进度
}
.response { response in
    // 处理响应
}

源码示例:Example/Source/MasterViewController.swift

进度精度优化

默认情况下,进度更新频率较高,可能导致UI频繁刷新。可通过ProgresscancellationHandler或自定义阈值控制更新频率:

var lastProgress: Double = 0

AF.upload(data, to: url)
    .uploadProgress { progress in
        let currentProgress = progress.fractionCompleted
        // 仅当进度变化超过1%时更新UI
        if abs(currentProgress - lastProgress) > 0.01 || currentProgress == 1.0 {
            lastProgress = currentProgress
            DispatchQueue.main.async {
                self.progressView.progress = Float(currentProgress)
            }
        }
    }

进度UI集成最佳实践

1. 表格中的进度显示

对于多任务列表,建议将进度信息存储在数据模型中,并使用UITableViewCell实时更新:

class TaskCell: UITableViewCell {
    @IBOutlet weak var progressView: UIProgressView!
    @IBOutlet weak var percentLabel: UILabel!
}

class TasksViewController: UITableViewController {
    var tasks: [DownloadTask] = []
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "TaskCell", for: indexPath) as! TaskCell
        let task = tasks[indexPath.row]
        
        cell.progressView.progress = Float(task.progress)
        cell.percentLabel.text = "\(Int(task.progress * 100))%"
        
        return cell
    }
}

2. 后台进度更新

长时间运行的任务应在后台线程更新进度,并通过DispatchQueue.main.async刷新UI:

AF.download(url, to: destination)
    .downloadProgress(queue: DispatchQueue.global()) { progress in
        // 在后台线程计算进度信息
        let progressInfo = ProgressInfo(
            completed: progress.completedUnitCount,
            total: progress.totalUnitCount,
            percent: progress.fractionCompleted
        )
        
        // 主线程更新UI
        DispatchQueue.main.async {
            self.updateUI(with: progressInfo)
        }
    }

常见问题与解决方案

问题1:进度不更新或卡在0%

可能原因

  • 服务器未返回Content-Length
  • 上传数据源为InputStream且未指定长度
  • 网络请求被暂停或取消

解决方案

// 上传InputStream时手动指定长度
let stream = InputStream(url: fileURL)!
let streamLength = try! FileManager.default.attributesOfItem(atPath: fileURL.path)[.size] as! Int64

var urlRequest = URLRequest(url: uploadURL)
urlRequest.httpBodyStream = stream
urlRequest.setValue("\(streamLength)", forHTTPHeaderField: "Content-Length")

AF.upload(urlRequest)
    .uploadProgress { progress in
        // 现在可以正常跟踪进度
    }

问题2:进度超过100%

可能原因

  • 服务器返回的Content-Length与实际文件大小不符
  • 多部分表单上传时计算错误

解决方案

.uploadProgress { progress in
    let clampedProgress = min(progress.fractionCompleted, 1.0)
    self.progressView.progress = Float(clampedProgress)
}

问题3:后台应用时进度停止更新

解决方案:使用后台会话配置:

let configuration = URLSessionConfiguration.background(withIdentifier: "com.example.background")
let session = Session(configuration: configuration)

session.download(url, to: destination)
    .downloadProgress { progress in
        // 后台下载时仍能接收进度更新
    }

高级用法:Documentation/AdvancedUsage.md#creating-a-session-with-a-urlsessionconfiguration

完整示例:文件传输管理器

以下是一个综合示例,展示如何构建一个功能完善的文件传输管理器,支持上传下载进度监控、暂停/继续和状态保存:

class FileTransferManager {
    static let shared = FileTransferManager()
    
    private var session: Session!
    private var activeRequests: [String: Request] = [:]
    
    init() {
        let configuration = URLSessionConfiguration.default
        configuration.timeoutIntervalForRequest = 30
        session = Session(configuration: configuration)
    }
    
    // 下载文件
    func downloadFile(url: URL, taskId: String, destination: @escaping DownloadRequest.Destination) {
        let request = session.download(url, to: destination)
            .downloadProgress { progress in
                NotificationCenter.default.post(name: .transferProgressUpdated, 
                                              object: nil, 
                                              userInfo: [
                                                "taskId": taskId,
                                                "progress": progress.fractionCompleted,
                                                "type": "download"
                                              ])
            }
            .response { response in
                self.activeRequests.removeValue(forKey: taskId)
                // 发送完成通知
            }
        
        activeRequests[taskId] = request
    }
    
    // 上传文件
    func uploadFile(data: Data, url: URL, taskId: String) {
        let request = session.upload(data, to: url)
            .uploadProgress { progress in
                NotificationCenter.default.post(name: .transferProgressUpdated,
                                              object: nil,
                                              userInfo: [
                                                "taskId": taskId,
                                                "progress": progress.fractionCompleted,
                                                "type": "upload"
                                              ])
            }
            .response { response in
                self.activeRequests.removeValue(forKey: taskId)
                // 发送完成通知
            }
        
        activeRequests[taskId] = request
    }
    
    // 取消任务
    func cancelTask(taskId: String) {
        activeRequests[taskId]?.cancel()
        activeRequests.removeValue(forKey: taskId)
    }
}

// 使用通知监听进度更新
NotificationCenter.default.addObserver(self, 
                                      selector: #selector(progressUpdated(_:)), 
                                      name: .transferProgressUpdated, 
                                      object: nil)

@objc func progressUpdated(_ notification: Notification) {
    guard let userInfo = notification.userInfo,
          let taskId = userInfo["taskId"] as? String,
          let progress = userInfo["progress"] as? Double,
          let type = userInfo["type"] as? String else { return }
    
    // 更新对应任务的进度UI
}

总结

Alamofire提供了简洁而强大的进度监控API,通过合理利用uploadProgressdownloadProgress方法,结合Progress对象的属性,我们可以轻松实现各种复杂的进度跟踪需求。关键要点包括:

  1. 根据数据源类型选择合适的上传/下载方法
  2. 始终在主线程更新UI进度
  3. 处理特殊情况(如缺少Content-Length头)
  4. 对于大型文件实现暂停/继续功能
  5. 使用通知或代理模式在复杂UI中传递进度信息

掌握这些技巧后,你可以为用户提供流畅直观的文件传输体验,显著提升应用质量和用户满意度。

示例项目:Example/Source/ 包含完整的上传下载进度演示

【免费下载链接】Alamofire Alamofire/Alamofire: Alamofire 是一个用于 iOS 和 macOS 的网络库,提供了 RESTful API 的封装和 SDK,可以用于构建网络应用程序和 Web 服务。 【免费下载链接】Alamofire 项目地址: https://gitcode.com/GitHub_Trending/al/Alamofire

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值