华过的痕迹

iOS开发网络请求——大文件的多线程断点下载

  iOS开发中网络请求技术已经是移动app必备技术,而网络中文件传输就是其中重点了。网络文件传输对移动客户端而言主要分为文件的上传和下载。作为开发者从技术角度会将文件分为小文件和大文件。小文件因为文件大小比较小导致传输所需时间少传输就快,因此不太容易影响用户体验,可用的技术就多。而大文件因为文件大小比较大导致传输时间长,因此就需要考虑到各种用户体验,比如避免在上传下载文件过程中阻塞主线程影响用户体验,就需要使用到多线程技术;为了给用户友好的进度提示,因此又需要开发中跟踪数据上传和下载数据的变化;为了提高用户体验,也需要考虑到断点续传的功能实现;而且大文件传输容易导致数据保持在内存中,又需要开发者处理内存中的数据;而为了处理多个文件或者压缩传输文件的数据大小,我们开发者还需要用到压缩和解压缩技术。根据不同的需求对大文件传输会有需要用到不同的解决方案,不过多线程断点续传是一个理想的在网络中传输大文件的方案。

小文件下载

  小文件的下载方式比较多,下面列出常用的下载方式:

    1.直接用NSData的+ (id)dataWithContentsOfURL:(NSURL *)url方法;
    2.利用NSURLConnection发送一个HTTP请求去下载
    3.利用NSURLSession发送一个HTTP请求去下载
    4.利用AFNetworking发送一个HTTP请求去下载
    4.如果是下载图片,还可以利用SDWebImage框架
大文件下载最佳解决方案———多线程断点下载
  实现思路:下载开始,创建一个和要下载的文件大小相同的文件(如果要下载的文件为100M,那么就在沙盒中创建一个100M的文件,然后计算每一段的下载量,开启多条线程下载各段的数据,分别写入对应的文件部分)。
  
  项目中用到的主要类如下:
完成的实现代码如下:
  
YYViewController.m
#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文件

#import "YYFileDownloader.h"
 
@implementation YYFileDownloader
@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文件

#import "YYFileDownloader.h"
 
@interface YYFileMultiDownloader : YYFileDownloader
 
@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

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

参考文章:http://www.cnblogs.com/wendingding/p/3947550.html

posted on 2015-12-21 01:41  华过的痕迹  阅读(703)  评论(0编辑  收藏  举报

导航