当前位置: > > > Swift - 网络抽象层库Moya的使用详解6(文件下载、资源下载器)

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)
    }
}
评论1
  • 1楼
    2018-08-30 17:43
    Distance先生

    请问站站,Moya如何支持断点下载呢?

    站长回复

    我也不知道办法,Moya好像就不支持断点下载,暂时帮不了你了。