Swift - 网络抽象层库Moya的使用详解6(文件下载、资源下载器)
九、文件下载的基本用法
1,网络层定义(MyServiceAPI.swift)
这里我只定义了一个枚举值 downloadAsset 用来表示资源下载请求,具体功能如下:
- 通过传入的资源名称(assetName)自动从 http://www.hangge.com/assets/ 路径下面下载对应的文件。
- 下载后文件保存在用户文档目录中,且不修改文件名(原来叫什么还叫什么)。
- 如果目录中已经存在有同名文件,新下载的文件不会覆盖老的。
import Moya //初始化请求的provider let MyServiceProvider = MoyaProvider<MyService>() //请求分类 public enum MyService { case downloadAsset(assetName:String) //下载文件 } //请求配置 extension MyService: TargetType { //服务器地址 public var baseURL: URL { return URL(string: "http://www.hangge.com")! } //各个请求的具体路径 public var path: String { switch self { case let .downloadAsset(assetName): return "/assets/\(assetName)" } } //请求类型 public var method: Moya.Method { return .get } //请求任务事件(这里附带上参数) public var task: Task { switch self { case .downloadAsset(_): return .downloadDestination(DefaultDownloadDestination) } } //是否执行Alamofire验证 public var validate: Bool { return false } //这个就是做单元测试模拟的数据,只会在单元测试文件中有作用 public var sampleData: Data { return "{}".data(using: String.Encoding.utf8)! } //请求头 public var headers: [String: String]? { return nil } } //定义下载的DownloadDestination(不改变文件名,同名文件不会覆盖) private let DefaultDownloadDestination: DownloadDestination = { temporaryURL, response in return (DefaultDownloadDir.appendingPathComponent(response.suggestedFilename!), []) } //默认下载保存地址(用户文档目录) let DefaultDownloadDir: URL = { let directoryURLs = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask) return directoryURLs.first ?? URL(fileURLWithPath: NSTemporaryDirectory()) }()
2,使用样例
比如我们这里传入一个"logo.png",Moya 便自动将 http://www.hangge.com/assets/logo.png 这张图片下载下来。
//要下载的图片名称 let assetName = "logo.png" //通过Moya进行下载 MyServiceProvider.request(.downloadAsset(assetName: assetName)) { result in switch result { case .success: let localLocation: URL = DefaultDownloadDir.appendingPathComponent(assetName) let image = UIImage(contentsOfFile: localLocation.path) print("下载完毕!保存地址:\(localLocation)") case let .failure(error): print(error) } }
3,运行结果
在上面使用样例代码中,我们在资源下载完毕后会将文件最终的保存位置打印出来,控制台输出信息如下:
十、文件下载的进阶用法
1,获取下载进度
(1)通过 progress 回调函数,我们可以实时得到当前的下传进度。
//要下载的图片名称 let assetName = "logo.png" //通过Moya进行下载 MyServiceProvider.request(.downloadAsset(assetName: assetName), progress:{ progress in //实时打印出下载进度 print("当前进度: \(progress.progress)") }) { result in switch result { case .success: let localLocation: URL = DefaultDownloadDir.appendingPathComponent(assetName) let image = UIImage(contentsOfFile: localLocation.path) print("下载完毕!保存地址:\(localLocation)") case let .failure(error): print(error) } }
(2)可以看到控制台不断输出已下载的进度(1 则表示下载完毕):
2,自动覆盖同名文件
如果想要下载时遇到同名的文件也能覆盖写入,在创建 DownloadDestination 对象时增加一个 .removePreviousFile 配置即可。
//定义下载的DownloadDestination(不改变文件名,遇到同名文件会覆盖) private let DefaultDownloadDestination: DownloadDestination = { temporaryURL, response in return (DefaultDownloadDir.appendingPathComponent(response.suggestedFilename!), [.removePreviousFile]) }
3,自定义文件保存的名字
(1)如果想要下载下来的文件改名,可以对 MyServiceAPI.swift 进行修改,在下载请求中增加一个参数(saveName),表示文件保存的名字。
import Moya //初始化请求的provider let MyServiceProvider = MoyaProvider<MyService>() //请求分类 public enum MyService { case downloadAsset(assetName:String, saveName:String) //下载文件 } //请求配置 extension MyService: TargetType { //服务器地址 public var baseURL: URL { return URL(string: "http://www.hangge.com")! } //各个请求的具体路径 public var path: String { switch self { case let .downloadAsset(assetName, _): return "/blog/images/\(assetName)" } } //请求类型 public var method: Moya.Method { return .get } //请求任务事件(这里附带上参数) public var task: Task { switch self { case let .downloadAsset(_, saveName): let localLocation: URL = DefaultDownloadDir.appendingPathComponent(saveName) let downloadDestination:DownloadDestination = { _, _ in return (localLocation, .removePreviousFile) } return .downloadDestination(downloadDestination) } } //是否执行Alamofire验证 public var validate: Bool { return false } //这个就是做单元测试模拟的数据,只会在单元测试文件中有作用 public var sampleData: Data { return "{}".data(using: String.Encoding.utf8)! } //请求头 public var headers: [String: String]? { return nil } } //默认下载保存地址(用户文档目录) let DefaultDownloadDir: URL = { let directoryURLs = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask) return directoryURLs.first ?? URL(fileURLWithPath: NSTemporaryDirectory()) }()
(2)下面是一个使用样例,我们将下载下来的文件自动以当前时间的时间戳命名。
//要下载的图片名称 let assetName = "logo.png" //保存的名称 let timeInterval:TimeInterval = Date().timeIntervalSince1970 let saveName = "\(Int(timeInterval)).png" //通过Moya进行下载 MyServiceProvider.request(.downloadAsset(assetName: assetName, saveName: saveName)) { result in switch result { case .success: let localLocation: URL = DefaultDownloadDir.appendingPathComponent(saveName) let image = UIImage(contentsOfFile: localLocation.path) print("下载完毕!保存地址:\(localLocation)") case let .failure(error): print(error) } }
(3)保存结果如下:
十一、封装一个资源下载器
有时我们会将一些资源文件(比如图片、声音、皮肤等)放置在服务器上,而不是直接打包到应用程序中,当程序要使用时资源时再从服务器上加载。这样的话以后如果需要修改图片等资源文件,只需把服务器上的文件替换即可,客户端这边就不需要再更新程序了。
客户端这边我们可以像上面那样直接通过 Moya 去请求资源(传入一个文件名,然后自动将其下载下来)。但这样做还是有些不方便的地方,可能我们的资源文件很多,要使用的地方也很多,每个地方都手动拼写文件名容易出错,也不容易记住。我们可以封装一个资源下载器方便使用。
1,资源下载器(AssetLoader.swift)
- 其本质还是通过 Moya 进行文件的下载,只不过这里我们将每一个资源都定义成一个枚举值。这样下载某个资源时只要调用对应的枚举值即可,方便使用。
- 同时在 MoyaProvider 外面再封装一层(AssetLoader),它会处理请求结果并返回文件存放路径。而且每次请求资源时会先判断本地是否已经存在该文件,如果已经下载过了就直接返回路径。
import Moya import Alamofire //默认下载保存地址(用户文档目录) fileprivate let assetDir: URL = { let directoryURLs = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask) return directoryURLs.first ?? URL(fileURLWithPath: NSTemporaryDirectory()) }() //资源分类 public enum Asset { case logo //logo图标 case star //星形图标 case checkmark //勾选图标 } //资源配置 extension Asset: TargetType { //根据枚举值获取对应的资源文件名 var assetName: String { switch self { case .logo: return "logo.png" case .star: return "star.png" case .checkmark: return "checkmark.png" } } //获取对应的资源文件本地存放路径 var localLocation: URL { return assetDir.appendingPathComponent(assetName) } //服务器地址 public var baseURL: URL { return URL(string: "http://www.hangge.com")! } //各个请求的具体路径 public var path: String { return "/assets/" + assetName } //请求类型 public var method: Moya.Method { return .get } //定义一个DownloadDestination var downloadDestination: DownloadDestination { return { _, _ in return (self.localLocation, .removePreviousFile) } } //请求任务事件 public var task: Task { return .downloadDestination(downloadDestination) } //是否执行Alamofire验证 public var validate: Bool { return false } //这个就是做单元测试模拟的数据,只会在单元测试文件中有作用 public var sampleData: Data { return "{}".data(using: String.Encoding.utf8)! } //请求头 public var headers: [String: String]? { return nil } } //资源下载器 final class AssetLoader { let provider = MoyaProvider<Asset>() init() { } func load(asset: Asset, completion: ((Result<Any>) -> Void)? = nil) { if FileManager.default.fileExists(atPath: asset.localLocation.path) { completion?(.success(asset.localLocation)) return } provider.request(asset) { result in switch result { case .success: completion?(.success(asset.localLocation)) case let .failure(error): completion?(.failure(error)) } } } }
2,使用样例
比如这里我们使用 AssetLoader 加载一个 logo 图标。
let loader = AssetLoader() loader.load(asset: .logo) { result in switch result { case let .success(localLocation): print("下载完毕!保存地址:\(localLocation)") case let .failure(error): print(error) } }
请问站站,Moya如何支持断点下载呢?