首页 > 代码库 > 使用NSURLConnection实现大文件断点下载

使用NSURLConnection实现大文件断点下载

使用NSURLConnection实现大文件断点下载

由于是实现大文件的断点下载,不是下载一般图片什么的.在设计这个类的时候本身就不会考虑把下载的文件缓存到内存中,而是直接写到文件系统.

要实现断点下载,需要满足1个条件,那就是,必须要服务器支持断点下载.

 

实现的思路是这样子的:

1.  第一次会获取到被下载文件的总大小(服务器提供这个值)

  下载文件总大小 = 期望从服务器获取文件的大小 + 本地已经下载的文件的大小

2.  设置请求的缓存策略为不会读取本地中已经缓存的数据(NSURLRequestReloadIgnoringLocalCacheData)

3.  在去服务器请求数据之前先获取到本地已经下载好的部分文件的长度,以这个参数设置进Range中到服务器去请求剩下的数据

4.  当从网络获取到一定的数据的时候,我们直接将数据写进文件系统中

YXDownloadNetwork.h

//
//  YXDownloadNetwork.h
//  Download
//
//  http://home.cnblogs.com/u/YouXianMing/
//
//  Copyright (c) 2014年 Y.X. All rights reserved.
//

#import <Foundation/Foundation.h>

// block的相关定义
typedef void (^downloadProgress_t)(long long currentBytes, long long totalBytes);
typedef void (^completion_t)(NSDictionary *headers, NSData *body);

@interface YXDownloadNetwork : NSObject

// 将block定义成属性
@property (nonatomic, copy) downloadProgress_t       downloadProgress;
@property (nonatomic, copy) completion_t             completion;

// 初始化方法
- (instancetype)initWithUrlString:(NSString *)urlString cacheCapacity:(unsigned long long)capacity;
- (void)start;

@end

YXDownloadNetwork.m

//
//  YXDownloadNetwork.m
//  Download
//
//  http://home.cnblogs.com/u/YouXianMing/
//
//  Copyright (c) 2014年 Y.X. All rights reserved.
//

#import "YXDownloadNetwork.h"

@interface YXDownloadNetwork ()<NSURLConnectionDelegate, NSURLConnectionDataDelegate>

@property (nonatomic, assign) unsigned long long   totalLength;      // 文件总大小
@property (nonatomic, assign) unsigned long long   startDataLength;  // 本地存在文件的大小
@property (nonatomic, assign) unsigned long long   expectedLength;   // 从服务器期望文件的大小
@property (nonatomic, assign) unsigned long long   cacheCapacity;    // 缓存文件容量,以k为单位

@property (nonatomic, strong) NSURLConnection     *dataConncetion;   // 网络连接
@property (nonatomic, strong) NSDictionary        *responseHeaders;  // 网络连接头部信息
@property (nonatomic, strong) NSFileHandle        *file;             // 文件操作句柄
@property (nonatomic, strong) NSMutableData       *cacheData;        // 用于缓存的data数据

@end

@implementation YXDownloadNetwork

- (instancetype)initWithUrlString:(NSString *)urlString cacheCapacity:(unsigned long long)capacity
{
    self = [super init];
    
    if (self)
    {
        // 获取缓存容量
        if (capacity <= 0)
        {
            _cacheCapacity = 100 * 1024;
        }
        else
        {
            _cacheCapacity = capacity * 1024;
        }
        
        // 获取用于缓存的数据
        _cacheData = http://www.mamicode.com/[NSMutableData new];
        
        // 获取文件名以及文件路径
        NSString *fileName = [urlString lastPathComponent];
        NSString *filePath =             fileFromPath([NSString stringWithFormat:@"/Documents/%@", fileName]);
        
        // 记录文件起始位置
        if ([[NSFileManager defaultManager] fileExistsAtPath:filePath])
        {
            // 从文件中读取出已经下载好的文件的长度
            _startDataLength = [[NSData dataWithContentsOfFile:filePath] length];
        }
        else
        {
            // 不存在则创建文件
            _startDataLength = 0;
            [[NSFileManager defaultManager] createFileAtPath:filePath
                                                    contents:nil
                                                  attributes:nil];
        }
        
        // 打开写文件流
        _file = [NSFileHandle fileHandleForWritingAtPath:filePath];
        
        // 创建一个网络请求
        NSMutableURLRequest* request =         [NSMutableURLRequest requestWithURL:[NSURL URLWithString:urlString]];
        
        // 禁止读取本地缓存
        [request setCachePolicy:NSURLRequestReloadIgnoringLocalCacheData];
        
        // 设置断点续传(需要服务器支持)
        [request setValue:[NSString stringWithFormat:@"bytes=%llu-", _startDataLength]
       forHTTPHeaderField:@"Range"];
        
        // 开始创建连接
        self.dataConncetion =         [[NSURLConnection alloc] initWithRequest:request
                                        delegate:self
                                startImmediately:NO];
    }
    
    return self;
}

- (void)start
{
    [self.dataConncetion start];
}

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
    if([response isKindOfClass:[NSHTTPURLResponse class]])
    {
        NSHTTPURLResponse *r = (NSHTTPURLResponse *)response;
        
        // 如果能获取到期望的数据长度就执行括号中的方法
        if ([r expectedContentLength] != NSURLResponseUnknownLength)
        {
            // 获取剩余要下载的
            _expectedLength  = [r expectedContentLength];
            
            // 计算出总共需要下载的
            _totalLength = _expectedLength + _startDataLength;

            // 获取头文件
            _responseHeaders = [r allHeaderFields];
        }
        else
        {
            NSLog(@"不支持断点下载");
        }
    }
}

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)theData
{
    // 追加缓存数据
    [_cacheData appendData:theData];
    
    // 如果该缓存数据的大小超过了指定的缓存大小
    if ([_cacheData length] >= _cacheCapacity)
    {
        // 移动到文件结尾
        [_file seekToEndOfFile];
        
        // 在文件末尾处追加数据
        [_file writeData:_cacheData];
        
        // 清空缓存数据
        [_cacheData setLength:0];
    }
    
    // 当前已经下载的所有数据的总量
    _startDataLength += [theData length];
    
    // 如果指定了block
    if (_downloadProgress)
    {
        _downloadProgress(_startDataLength, _totalLength);
    }
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
    // 移动到文件结尾
    [_file seekToEndOfFile];
    
    // 在文件末尾处追加最后的一点缓存数据
    [_file writeData:_cacheData];
    
    // 清空缓存
    [_cacheData setLength:0];
    
    NSLog(@"下载完成哦");
}

NS_INLINE NSString * fileFromPath(NSString *filePath)
{
    return [NSHomeDirectory() stringByAppendingString:filePath];
}

@end

测试代码如下:

实际上这个类还有很多地方不完善,但至少能起到抛砖引玉的作用,它更牛逼的用途靠你来修改了,亲.