自定义下载器

2020-03-30  本文已影响0人  AndyYaWei

功能:

文件结构:

技术要点:

let stream = OutputStream(toFileAtPath: fullPath, append: true)
self.stream = stream
self.stream?.open()
let bytes = [UInt8](data)
self.stream?.write(UnsafePointer<UInt8>(bytes), maxLength: bytes.count)
self.stream?.close()
self.stream = nil
request.setValue(NSString(format: "bytes=%lld-", self.fileCurrentSize) as String, forHTTPHeaderField: "Range")

下载路径

下载器UML.png

AYFileTool

获取文件大小

static func getFileSize(filePath: String) -> Int64 {
        if !FileManager.default.fileExists(atPath: filePath) {
            return 0
        }
        var fileDict = [FileAttributeKey: Any]()
        do {
          fileDict = try FileManager.default.attributesOfItem(atPath: filePath)
        } catch(let error) {
            print("error========\(error.localizedDescription)")
            return 0
        }
        return fileDict[FileAttributeKey.size] as? Int64 ?? 0
    }

删除文件

static func removeFile(filePath: String) {
        do {
            try FileManager.default.removeItem(atPath: filePath)
        } catch (let error) {
            print("error========\(error.localizedDescription)")
        }
    }

转换文件大小

static func calculateFileSizeInUnit(contentLength: Double) -> CGFloat {
        if contentLength >= pow(1024, 3) {
            return CGFloat(contentLength) / (pow(1024, 3))
        } else if contentLength > pow(1024, 2) {
            return CGFloat(contentLength) / (pow(1024, 2))
        } else if contentLength > 1024 {
            return CGFloat(contentLength) / 1024
        } else {
            return CGFloat(contentLength)
        }
    }

转换单位

 static func calculateUnit(contentLength: Double) -> String {
        if contentLength >= pow(1024, 3) {
            return "GB"
        } else if contentLength >= pow(1024, 2) {
            return "MB"
        } else if contentLength >= 1024 {
            return "KB"
        } else {
            return "Bytes"
        }
    }

AYDownLoader

下载方法

func downLoad(url: URL?, progressBlock: ((_ progress: CGFloat) -> Void)?, successBlock: ((_ downLoadPath : String) -> Void)?, failBlock: (() -> Void)?) {

        downLoadURL = url
        self.progressBlock = progressBlock
        self.successBlock = successBlock
        self.failBlock = failBlock

        if self.isDowning {
            print("正在下载....")
            return
        }

        // 1. 获取需要下载的文件头信息
        let result = getRemoteFileMessage()
        if !result {
            print("下载出错,请重新尝试")
            self.failBlock?()
            isDowning = false
            return
        }

        // 2. 根据需要下载的文件头信息,验证本地信息
        // 2.1 如果本地文件存在
        //           进行一下验证:
        //              文件大小 == 服务器文件大小;文件已经存在,不需要处理
        //              文件大小 > 服务器文件大小;删除本地文件,重新下载
        //              文件大小 < 服务器文件大小;根据本地缓存,继续断点下载
        // 2.2 如果文件不存在,则直接下载

        let isRequireDownLoad = checkLocalFile()
        if isRequireDownLoad {
            print("根据文件缓存大小, 执行下载操作")
            startDownLoad()
        } else {
            print("文件已经存在---\(String(describing: self.fileFullPath))")
            self.successBlock?(self.fileFullPath!)
        }

    }

开始下载

func startDownLoad() {
        isDowning = true
        let session = URLSession(configuration: URLSessionConfiguration.default, delegate: self, delegateQueue: OperationQueue.main)

        guard let url = self.downLoadURL else { return  }
        let request = NSMutableURLRequest(url: url)
        request.setValue(NSString(format: "bytes=%lld-", self.fileCurrentSize) as String, forHTTPHeaderField: "Range")

        self.downLoadTask = session.dataTask(with: request as URLRequest)
        self.downLoadTask?.resume()
    }

暂停下载

func pauseDownLoad() {
    isDowning = false
    self.downLoadTask?.suspend()
}

继续下载

func resumeDownLoad() {
        print("继续")
        isDowning = true
        if self.downLoadTask != nil {
            self.downLoadTask!.resume()
        } else {
            downLoad(url: self.downLoadURL, progressBlock: self.progressBlock, successBlock: self.successBlock, failBlock: self.failBlock)
        }
    }

取消下载

 func cancelDownLoad() {

        print("取消")
        isDowning = false
        self.downLoadTask?.cancel()
        print("-----\(String(describing: self.downLoadTask?.state))")
        self.downLoadTask = nil

        try? FileManager.default.removeItem(atPath: self.fileFullPath ?? "")
    }

获取下载文件的信息

func getRemoteFileMessage() -> Bool {

        // 对信息进行本地缓存, 方便下次使用
        let headerMsgPath = (kLocalPath as NSString).appendingPathComponent(kHeaderFilePath)

        guard let fileName = self.downLoadURL?.lastPathComponent else {
            return false
        }

        var dic = NSMutableDictionary(contentsOfFile: headerMsgPath)
        if dic == nil {
            dic = NSMutableDictionary()
        }

        let containsKey = dic?.allKeys.contains {
            return $0 as? String == fileName
        }

        if let isContains = containsKey, isContains == true  {
            self.fileTotalSize = (dic?[fileName] as? Int64) ?? 0
            self.fileFullPath = (kLocalPath as NSString).appendingPathComponent(fileName)
            return true
        }

        guard let url = self.downLoadURL else {
            return false
        }

        var isCanGet = false
        var request = URLRequest(url: url, cachePolicy: NSURLRequest.CachePolicy.reloadIgnoringLocalCacheData, timeoutInterval: 30.0)
        request.httpMethod = "HEAD"

        // 使用信号量-同步请求
        let semaphore = DispatchSemaphore(value: 0)
        let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
            if error == nil {
                self.fileTotalSize = Int64((response?.expectedContentLength) ?? 0)
                if let suggestedFilename = response?.suggestedFilename {
                    self.fileFullPath = (kLocalPath as NSString).appendingPathComponent(suggestedFilename)
                }
                dic?.setValue(self.fileTotalSize, forKey: fileName)
                dic?.write(toFile: headerMsgPath, atomically: true)
                isCanGet = true
            } else {
                isCanGet = false
            }
            semaphore.signal()
        }
        task.resume()
        semaphore.wait()
        return isCanGet
    }

获取文件大小

static func cacheFileSize(url: URL) -> Int64 {
        let path = (kLocalPath as NSString).appendingPathComponent(url.lastPathComponent)
        return AYFileTool.getFileSize(filePath: path)
    }

删除文件

static func removeCacheFile(url: URL){
        let path = (kLocalPath as NSString).appendingPathComponent(url.lastPathComponent)
        AYFileTool.removeFile(filePath: path)
    }

检测文件是否需要下载

 func checkLocalFile() -> Bool {
        guard let fullPath = self.fileFullPath else {
            print("路径有问题")
            return false
        }

        self.fileCurrentSize = AYFileTool.getFileSize(filePath: fullPath)

        if self.fileCurrentSize > self.fileTotalSize {
            // 删除文件,并重新下载
            AYFileTool.removeFile(filePath: fullPath)
            return true
        }

        if self.fileCurrentSize < self.fileTotalSize {
            return true
        }

        return false
    }

AYDownLoadManager

根据key获取下载器

  func loader(url: URL?) -> AYDownLoader? {
        guard let uri = url else {
            return nil
        }
        return self.downLoadDic[uri.lastPathComponent]
    }

下载方法

 func downLoad(url: URL, progressBlock: @escaping ((_ progress: CGFloat) -> Void), successBlock: @escaping ((_ fileFullPath: String) -> Void), failBlock: @escaping (() -> Void)) {

        /*
         var downLoader = self.loader(url: url)
         if let loader = downLoader {
         loader.resumeDownLoad()
         } else {
         downLoader = AYDownLoader()
         self.downLoadDic[url.lastPathComponent] = downLoader!
         downLoader?.downLoad(url: url, progressBlock: { (progress) in
         progressBlock(progress)
         }, successBlock: { [weak self] (downLoadPath :String) in
         guard let weakSelf = self else { return }
         successBlock(downLoadPath)
         // 移除对象
         weakSelf.downLoadDic.removeValue(forKey: (downLoadPath as NSString).lastPathComponent)
         }, failBlock: {
         failBlock()
         })*/

        var downLoader = self.loader(url: url)
        if downLoader == nil {
            downLoader = AYDownLoader()
        }
        self.downLoadDic[url.lastPathComponent] = downLoader!
        downLoader?.downLoad(url: url, progressBlock: { (progress) in
            progressBlock(progress)
        }, successBlock: { [weak self] (downLoadPath :String) in
            guard let weakSelf = self else { return }
            successBlock(downLoadPath)
            // 移除对象
            weakSelf.downLoadDic.removeValue(forKey: (downLoadPath as NSString).lastPathComponent)
            }, failBlock: {
                failBlock()
        })

    }

暂停下载

 func pauseDownLoad(url: URL){
        let downloader = loader(url: url)
        downloader?.pauseDownLoad()
    }

继续下载

func resumeDownLoad(url: URL){
        let downloader = loader(url: url)
        downloader?.resumeDownLoad()
    }

取消下载

func cancelDownLoad(url: URL) {
        let downLoader = downLoadDic[url.lastPathComponent]
        if let loader = downLoader {
            loader.cancelDownLoad()
        } else {
            AYDownLoader.removeCacheFile(url: url)
        }
    }

取消所有下载

func cancelAllTasks() {
        self.downLoadDic.forEach { (key, value) in
            (value as AYDownLoader).cancelDownLoad()
            downLoadDic.removeValue(forKey: key)
        }
    }

暂停下载

func pauseAllTasks(){
        self.downLoadDic.forEach { (key, value) in
            (value as AYDownLoader).pauseDownLoad()
        }
    }
上一篇 下一篇

猜你喜欢

热点阅读