iOS开发网络篇 一一 NSURLConnection-大文件断点下载


实现思路:

在下载文件的时候不再是整块的从头开始下载,而是看当前文件已经下载到哪个地方,然后从该地方接着往后面下载。可以通过在请求对象中设置请求头实现。

解决方案:

//2.创建请求对象
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];

    //2.1 设置下载文件的某一部分
    // 只要设置HTTP请求头的Range属性, 就可以实现从指定位置开始下载
    /*
     表示头500个字节:Range: bytes=0-499
     表示第二个500字节:Range: bytes=500-999
     表示最后500个字节:Range: bytes=-500
     表示500字节以后的范围:Range: bytes=500-
     */
    NSString *range = [NSString stringWithFormat:@"bytes=%zd-",self.currentLength];
    [request setValue:range forHTTPHeaderField:@"Range"];


注意点:  下载进度并判断是否需要重新创建文件

//获得当前要下载文件的总大小(通过响应头得到)
    NSHTTPURLResponse *res = (NSHTTPURLResponse *)response;

    //注意点:res.expectedContentLength获得是本次请求要下载的文件的大小(并非是完整的文件的大小)
    //因此:文件的总大小 == 本次要下载的文件大小+已经下载的文件的大小
    self.totalLength = res.expectedContentLength + self.currentLength;

    NSLog(@"----------------------------%zd",self.totalLength);

    //0 判断当前是否已经下载过,如果当前文件已经存在,那么直接返回
    if (self.currentLength >0) {
        return;
    }


代码:

//
//  Created by 朝阳 on 2017/12/11.
//  Copyright © 2017年 sunny. All rights reserved.
//

#import "ViewController.h"

@interface ViewController ()<NSURLConnectionDataDelegate>

@property (nonatomic, assign) NSInteger totalSize;
@property (nonatomic, assign) NSInteger currentSize;
@property (weak, nonatomic) IBOutlet UISlider *slider;

/** 沙盒路径 */
@property (nonatomic,strong) NSString *fullPath;
/** 文件句柄*/
@property (nonatomic, strong)NSFileHandle *handle;
/** 连接对象 */
@property (nonatomic, strong) NSURLConnection *connect;


@end

@implementation ViewController

- (IBAction)startDownload:(id)sender
{
    [self download];
}

- (IBAction)cancelDownload:(id)sender
{
    [self.connect cancel];
}

- (IBAction)goOnDownload:(id)sender
{
    [self download];
}

// 内容飙升的原因: self.fileData是一个变量. 把从网络中的数据保存到了fileData中,并不会释放
// self.fileData 在写入到沙盒中, 解决内存飙升问题: 直接把数据写入到沙盒中,不通过fileData
- (void)download
{
    //1. url
    NSURL *url = [NSURL URLWithString:@"http://localhost:8080/MJServer/resources/videos/minion_01.mp4"];
    //2. 创建请求对象
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    
    //设置请求头信息,告诉服务器值请求一部分数据range
    /*
     bytes=0-100
     bytes=-100
     bytes=0- 请求100之后的所有数据
     */
    // 从文件的部分开始下载
    NSString *range = [NSString stringWithFormat:@"bytes=%ld-",self.currentSize];
    [request setValue:range forHTTPHeaderField:@"Range"];
    
    //3. 发送请求(代理)
    self.connect = [[NSURLConnection alloc] initWithRequest:request delegate:self];
    
}

#pragma -mark NSURLConnectionDataDelegate
// 当接收到服务器响应的时候调用
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
    // self.totalSize放在 if判断前
    //1. 得到 文件的总大小(本次请求的文件数据的总大小)
    // 本次请求的文件数据大小 != 文件总大小(如果再次发送请求的时候,此时的self.totalSize 就小于文件的大小,因此在\
    在后面计算 1.0 * self.currentSize / self.totalSize 的时候,会出现数据错乱) 因此要加上当前已经下载的数据.
    self.totalSize = response.expectedContentLength + self.currentSize;
    
    // 当self.currentSize已经下载部分数据后,就不继续往后走了.
    // 不作判断的话, 就会继续创建一个空文件, 然后后面的数据就下载到另一个空文件去了.
    if (self.currentSize > 0) {
        return;
    }
    
    NSLog(@"didReceiveResponse");
    
    // self.totalSize 放在if判断后.(此时只发送一次请求,所有self.totalSize就是文件的大小)
    //1. 得到 文件的总大小(本次请求的文件数据的总大小)
//    self.totalSize = response.expectedContentLength;
    
    //2. 写数据到沙盒中
    self.fullPath = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"1.mp4"];
    
    //3. 创建一个空文件
    [[NSFileManager defaultManager] createFileAtPath:self.fullPath contents:nil attributes:nil];
    //4. 创建文件句柄(指针)
    self.handle = [NSFileHandle fileHandleForWritingAtPath:self.fullPath];
}

// 当接收到服务器返回数据的时候调用-并调用多次(数据是一点点返回的)
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
    // 这样直接把数据 写入到沙盒路径中,是不正确的. 会把下载的前面下载的data 给 覆盖掉
    //[data writeToFile:self.fullPath atomically:YES];
    
    //1. 移动文件句柄到每次data的末尾
    [self.handle seekToEndOfFile];
    //2. 写数据
    [self.handle writeData:data];
    //3. 获得进度
    self.currentSize += data.length;
    
    // 下载进度 = 已经下载 / self.totalSize
    self.slider.value = 1.0 * self.currentSize / self.totalSize;
    
    NSLog(@"%f",1.0 * self.currentSize / self.totalSize);
}

// 当发送请求失败的时候调用
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
    NSLog(@"%@",error);
}

// 当发送请求完成后调用
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
    //1. 关闭文件句柄
    [self.handle closeFile];
    self.handle = nil;
    NSLog(@"connectionDidFinishloading");
    NSLog(@"%@",self.fullPath);
}

@end

使用输出流也可以实现NSFileHandle的功能

//  Created by 朝阳 on 2017/12/11.
//  Copyright © 2017年 sunny. All rights reserved.
//

#import "ViewController.h"

@interface ViewController ()<NSURLConnectionDataDelegate>

@property (nonatomic, assign) NSInteger totalSize;
@property (nonatomic, assign) NSInteger currentSize;
@property (weak, nonatomic) IBOutlet UISlider *slider;

/** 沙盒路径 */
@property (nonatomic,strong) NSString *fullPath;
/** 连接对象 */
@property (nonatomic, strong) NSURLConnection *connect;
/* 输出流对象 */
@property (nonatomic, strong) NSOutputStream *stream;


@end

@implementation ViewController

- (IBAction)startDownload:(id)sender
{
    [self download];
}

- (IBAction)cancelDownload:(id)sender
{
    [self.connect cancel];
}

- (IBAction)goOnDownload:(id)sender
{
    [self download];
}

// 内容飙升的原因: self.fileData是一个变量. 把从网络中的数据保存到了fileData中,并不会释放
// self.fileData 在写入到沙盒中, 解决内存飙升问题: 直接把数据写入到沙盒中,不通过fileData
- (void)download
{
    //1. url
    NSURL *url = [NSURL URLWithString:@"http://flv2.bn.netease.com/videolib3/1604/28/fVobI0704/SD/fVobI0704-mobile.mp4"];
    //2. 创建请求对象
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    
    //设置请求头信息,告诉服务器值请求一部分数据range
    /*
     bytes=0-100
     bytes=-100
     bytes=0- 请求100之后的所有数据
     */
    // 从文件的部分开始下载
    NSString *range = [NSString stringWithFormat:@"bytes=%ld-",self.currentSize];
    [request setValue:range forHTTPHeaderField:@"Range"];
    
    //3. 发送请求(代理)
    self.connect = [[NSURLConnection alloc] initWithRequest:request delegate:self];
    
}

#pragma -mark NSURLConnectionDataDelegate
// 当接收到服务器响应的时候调用
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
    NSLog(@"didReceiveResponse");
    self.totalSize = response.expectedContentLength + self.currentSize;
    
    // 当self.currentSize已经下载部分数据后,就不继续往后走了.
    // 不作判断的话, 就会继续创建一个空文件, 然后后面的数据就下载到另一个空文件去了.
    if (self.currentSize > 0) {
        return;
    }
    
    //2. 写数据到沙盒中
    // response.suggestedFilename 获取资源的名称
    self.fullPath = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:response.suggestedFilename];
    
    //3. 创建输出流
    /*
     param1: 文件的路径
     param2: YES 追加
     特点: 如果该输出流指向的地址没有文件,那么会自动创建一个空的文件
     */
    NSOutputStream *stream = [[NSOutputStream alloc] initToFileAtPath:self.fullPath append:YES];
    // 开启输出流
    [stream open];
    self.stream = stream;
    
}

// 当接收到服务器返回数据的时候调用-并调用多次(数据是一点点返回的)
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
    // 写数据
    [self.stream write:data.bytes maxLength:data.length];
    
    // 获得进度
    self.currentSize += data.length;
    
    // 下载进度 = 已经下载 / self.totalSize
    self.slider.value = 1.0 * self.currentSize / self.totalSize;
    
    NSLog(@"%f",1.0 * self.currentSize / self.totalSize);
}

// 当发送请求失败的时候调用
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
    NSLog(@"%@",error);
}

// 当发送请求完成后调用
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
    //关闭流
    [self.stream close];
    self.stream = nil;
    
    NSLog(@"connectionDidFinishloading");
    NSLog(@"%@",self.fullPath);
}

@end

使用多线程下载文件思路
01 开启多条线程,每条线程都只下载文件的一部分(通过设置请求头中的Range来实现)

02 创建一个和需要下载文件大小一致的文件,判断当前是那个线程,根据当前的线程来判断下载的数据应该写入到文件中的哪个位置。(假设开5条线程来下载10M的文件,那么线程1下载0-2M,线程2下载2-4M一次类推,当接收到服务器返回的数据之后应该先判断当前线程是哪个线程,假如当前线程是线程2,那么在写数据的时候就从文件的2M位置开始写入)

03 代码相关:使用NSFileHandle这个类的seekToFileOfSet方法,来向文件中特定的位置写入数据。

04 技术相关
    a.每个线程通过设置请求头下载文件中的某一个部分
    b.通过NSFileHandle向文件中的指定位置写数据

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

white camel

感谢支持~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值