• iOS开发网络篇—多线程断点下载


    iOS开发网络篇—多线程断点下载

    说明:本文介绍多线程断点下载。项目中使用了苹果自带的类,实现了同时开启多条线程下载一个较大的文件。因为实现过程较为复杂,所以下面贴出完整的代码。

    实现思路:下载开始,创建一个和要下载的文件大小相同的文件(如果要下载的文件为100M,那么就在沙盒中创建一个100M的文件,然后计算每一段的下载量,开启多条线程下载各段的数据,分别写入对应的文件部分)。

    项目中用到的主要类如下:

     

    完成的实现代码如下:

    主控制器中的代码:

    #import "YYViewController.h"
    #import "YYFileMultiDownloader.h"
    
    @interface YYViewController ()
    @property (nonatomic, strong) YYFileMultiDownloader *fileMultiDownloader;
    @end
    
    @implementation YYViewController
    -  (YYFileMultiDownloader *)fileMultiDownloader
    {
        if (!_fileMultiDownloader) {
            _fileMultiDownloader = [[YYFileMultiDownloader alloc] init];
            // 需要下载的文件远程URL
            _fileMultiDownloader.url = @"http://192.168.1.200:8080/MJServer/resources/jre.zip";
            // 文件保存到什么地方
            NSString *caches = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
            NSString *filepath = [caches stringByAppendingPathComponent:@"jre.zip"];
            _fileMultiDownloader.destPath = filepath;
        }
        return _fileMultiDownloader;
    }
    
    - (void)viewDidLoad
    {
        [super viewDidLoad];
        
    }
    
    - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
    {
        [self.fileMultiDownloader start];
    }
    
    @end

    自定义一个基类

    YYFileDownloader.h文件

    #import <Foundation/Foundation.h>
    
    @interface YYFileDownloader : NSObject
    {
        BOOL _downloading;
    }
    /**
     * 所需要下载文件的远程URL(连接服务器的路径)
     */
    @property (nonatomic, copy) NSString *url;
    /**
     * 文件的存储路径(文件下载到什么地方)
     */
    @property (nonatomic, copy) NSString *destPath;
    
    /**
     * 是否正在下载(有没有在下载, 只有下载器内部才知道)
     */
    @property (nonatomic, readonly, getter = isDownloading) BOOL downloading;
    
    /**
     * 用来监听下载进度
     */
    @property (nonatomic, copy) void (^progressHandler)(double progress);
    
    /**
     * 开始(恢复)下载
     */
    - (void)start;
    
    /**
     * 暂停下载
     */
    - (void)pause;
    @end

    YYFileDownloader.m文件

    1 #import "YYFileDownloader.h"
    2 
    3 @implementation YYFileDownloader
    4 @end

    下载器类继承自YYFileDownloader这个类

    YYFileSingDownloader.h文件

    #import "YYFileDownloader.h"
    
    @interface YYFileSingleDownloader : YYFileDownloader
    /**
     *  开始的位置
     */
    @property (nonatomic, assign) long long begin;
    /**
     *  结束的位置
     */
    @property (nonatomic, assign) long long end; 
    @end

    YYFileSingDownloader.m文件

    #import "YYFileSingleDownloader.h"
    @interface YYFileSingleDownloader() <NSURLConnectionDataDelegate>
    /**
     * 连接对象
     */
    @property (nonatomic, strong) NSURLConnection *conn;
    
    /**
     *  写数据的文件句柄
     */
    @property (nonatomic, strong) NSFileHandle *writeHandle;
    /**
     *  当前已下载数据的长度
     */
    @property (nonatomic, assign) long long currentLength;
    @end
    
    @implementation YYFileSingleDownloader
    
    - (NSFileHandle *)writeHandle
    {
        if (!_writeHandle) {
            _writeHandle = [NSFileHandle fileHandleForWritingAtPath:self.destPath];
        }
        return _writeHandle;
    }
    
    /**
     * 开始(恢复)下载
     */
    - (void)start
    {
        NSURL *url = [NSURL URLWithString:self.url];
        // 默认就是GET请求
        NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
        // 设置请求头信息
        NSString *value = [NSString stringWithFormat:@"bytes=%lld-%lld", self.begin + self.currentLength, self.end];
        [request setValue:value forHTTPHeaderField:@"Range"];
        self.conn = [NSURLConnection connectionWithRequest:request delegate:self];
        
        _downloading = YES;
    }
    
    /**
     * 暂停下载
     */
    - (void)pause
    {
        [self.conn cancel];
        self.conn = nil;
        
        _downloading = NO;
    }
    
    
    #pragma mark - NSURLConnectionDataDelegate 代理方法
    /**
     *  1. 当接受到服务器的响应(连通了服务器)就会调用
     */
    - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
    {
        
    }
    
    /**
     *  2. 当接受到服务器的数据就会调用(可能会被调用多次, 每次调用只会传递部分数据)
     */
    - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
    {
        // 移动到文件的尾部
        [self.writeHandle seekToFileOffset:self.begin + self.currentLength];
        // 从当前移动的位置(文件尾部)开始写入数据
        [self.writeHandle writeData:data];
        
        // 累加长度
        self.currentLength += data.length;
        
        // 打印下载进度
        double progress = (double)self.currentLength / (self.end - self.begin);
        if (self.progressHandler) {
            self.progressHandler(progress);
        }
    }
    
    /**
     *  3. 当服务器的数据接受完毕后就会调用
     */
    - (void)connectionDidFinishLoading:(NSURLConnection *)connection
    {
        // 清空属性值
        self.currentLength = 0;
        
        // 关闭连接(不再输入数据到文件中)
        [self.writeHandle closeFile];
        self.writeHandle = nil;
    }
    
    /**
     *  请求错误(失败)的时候调用(请求超时断网没有网, 一般指客户端错误)
     */
    - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
    {
        
    }
    
    @end

    设计多线程下载器(利用HMFileMultiDownloader能开启多个线程同时下载一个文件)

    一个多线程下载器只下载一个文件

    YYFileMultiDownloader.h文件

    1 #import "YYFileDownloader.h"
    2 
    3 @interface YYFileMultiDownloader : YYFileDownloader
    4 
    5 @end

    YYFileMultiDownloader.m文件

    #import "YYFileMultiDownloader.h"
    #import "YYFileSingleDownloader.h"
    
    #define YYMaxDownloadCount 4
    
    @interface YYFileMultiDownloader()
    @property (nonatomic, strong) NSMutableArray *singleDownloaders;
    @property (nonatomic, assign) long long totalLength;
    @end
    
    @implementation YYFileMultiDownloader
    
    - (void)getFilesize
    {
        NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:self.url]];
        request.HTTPMethod = @"HEAD";
        
        NSURLResponse *response = nil;
    #warning 这里要用异步请求
        [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:nil];
        self.totalLength = response.expectedContentLength;
    }
    
    - (NSMutableArray *)singleDownloaders
    {
        if (!_singleDownloaders) {
            _singleDownloaders = [NSMutableArray array];
            
            // 获得文件大小
            [self getFilesize];
            
            // 每条路径的下载量
            long long size = 0;
            if (self.totalLength % YYMaxDownloadCount == 0) {
                size = self.totalLength / YYMaxDownloadCount;
            } else {
                size = self.totalLength / YYMaxDownloadCount + 1;
            }
            
            // 创建N个下载器
            for (int i = 0; i<YYMaxDownloadCount; i++) {
                YYFileSingleDownloader *singleDownloader = [[YYFileSingleDownloader alloc] init];
                singleDownloader.url = self.url;
                singleDownloader.destPath = self.destPath;
                singleDownloader.begin = i * size;
                singleDownloader.end = singleDownloader.begin + size - 1;
                singleDownloader.progressHandler = ^(double progress){
                    NSLog(@"%d --- %f", i, progress);
                };
                [_singleDownloaders addObject:singleDownloader];
            }
            
            // 创建一个跟服务器文件等大小的临时文件
            [[NSFileManager defaultManager] createFileAtPath:self.destPath contents:nil attributes:nil];
            
            // 让self.destPath文件的长度是self.totalLengt
            NSFileHandle *handle = [NSFileHandle fileHandleForWritingAtPath:self.destPath];
            [handle truncateFileAtOffset:self.totalLength];
        }
        return _singleDownloaders;
    }
    
    /**
     * 开始(恢复)下载
     */
    - (void)start
    {
        [self.singleDownloaders makeObjectsPerformSelector:@selector(start)];
        
        _downloading = YES;
    }
    
    /**
     * 暂停下载
     */
    - (void)pause
    {
        [self.singleDownloaders makeObjectsPerformSelector:@selector(pause)];
        _downloading = NO;
    }
    
    @end

     补充说明:如何获得将要下载的文件的大小?

     

  • 相关阅读:
    Lightmaping
    Android内存回收机制
    基本光照模型简单实现
    Pass的通用指令开关
    使用Depth Texture
    使用替换shader渲染
    Windows下安装Oracle12C(一)
    SpringMVC文件上传基础
    Spring集成线程池
    《经久不衰的Spring框架:@ResponseBody 中文乱码》(转)
  • 原文地址:https://www.cnblogs.com/yipingios/p/5562676.html
Copyright © 2020-2023  润新知