使用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 = [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

测试代码如下:

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

目录
相关文章
|
8月前
|
iOS开发
iOS开发 GET、POST请求方法:NSURLSession篇
iOS开发 GET、POST请求方法:NSURLSession篇
74 0
|
10月前
|
前端开发 Python 微服务
flask.send_file实现文件下载、文件传输和二进制流传输
在使用flask框架时,我们有时需要向前端传输文件。或者需要用户访问一个url时直接下载文件。这时可以使用flask.send_file()函数来实现相关的操作。
1171 0
|
缓存 C# 图形学
C#多线程下载、断点续传的实现
做Unity热更功能的时候,发现单线程下载大尺寸资源文件的效率太低,专门去研究了下多线程下载,这里记录下相关知识点。
HttpWebRequest(跨域下载文件——网络流转换为内存流下载)
1.Stream 转换为 MemoryStream(Stream不支持查找) MemoryStream StreamToMemoryStream(Stream instream) { MemoryStream outstream = new Mem...
1150 0
|
Android开发
OkHttp3下载文件失败
场景:服务端将文件放入输出流中,安卓端采用OkHttp3下载失败,但同样的方法可以下载百度上的文件。 原因:经排查,为安卓端采用OkHttp3下载调用进度条获取不到文件大小而出错,而进度条获取文件大小通过文件头“Content-Length”获取,推测服务端没有传该参数。 纠错:服务端输出文件流的时候添加文件头。 //HttpServletResponse resp res
2904 0
多线程断点下载文件
package cn.itcast.download; import java.io.File; import java.io.InputStream; import java.io.RandomAccessFile; import java.net.HttpURLConnection; import java.net.URL; public class MulThreadDownloa
1378 0