iOS网络----简单下载/断点续传/后台下载

目录

1.断点续传概述;
2.断点续传原理;
3.简单下载的实现;
4.实现断点续传;
5.实现后台下载

第一: 断点续传概述

断点续传就是从文件上次中断的地方开始重新下载或上传数据,而不是从文件开头。

第二: 断点续传原理

要实现断点续传,服务器必须支持。目前最常见的是两种方式:FTP 和 HTTP。下面来简单介绍 HTTP 断点续传的原理:

断点续传主要依赖于HTTP 头部定义的 Range 来完成。具体 Range 的说明参见 RFC2616中 14.35.2 节。有了 Range,应用可以通过 HTTP 请求获取当前下载资源的的位置进而来恢复下载该资源。Range 的定义如图 1 所示:

图片一
图片二

在上面的例子中的“Range: bytes=1208765-”表示请求资源开头 1208765 字节之后的部分。

图片三

上面例子中的”Accept-Ranges: bytes”表示服务器端接受请求资源的某一个范围,并允许对指定资源进行字节类型访问。”Content-Range: bytes 1208765-20489997/20489998”说明了返回提供了请求资源所在的原始实体内的位置,还给出了整个资源的长度。这里需要注意的是 HTTP return code 是 206 而不是 200。

第三.简单下载的实现;

大家用惯了AFN, 可能对于系统原生的不大熟悉, 为了直观, 先回顾一些系统原生的东西----知其然知其所以然

以下代码在当前的currentSession中创建一个网络请求任务---可以取消的任务, 并下载一个图片展示出来;

效果图为

Paste_Image.png
#pragma mark 开始下载
- (IBAction)startDownload:(id)sender {

    if (!self.cancelDownloadTask) {
        self.imageView.image = nil;
        NSString *imageURLStr = @"http://upload-images.jianshu.io/upload_images/326255-2834f592d7890aa6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240";
        //创建网络请求
        NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:imageURLStr]];
        //将当前session中创建下载取消的任务
        self.cancelDownloadTask = [self.currentSession downloadTaskWithRequest:request];
        //保证下载按钮只点击一次
        [self setDownloadButtonsWithEnabled:NO];
        //开始
        [self.cancelDownloadTask resume];
    }

}

下载的代理

创建了网络请求, 之后主要的任务都在于下载的代理中做相应的任务.

#pragma mark 下载的代理

/**
 *  每次写入沙盒完毕调用
 *  在这里面监听下载进度,totalBytesWritten/totalBytesExpectedToWrite
 *
 *  @param bytesWritten              这次写入的大小
 *  @param totalBytesWritten         已经写入沙盒的大小
 *  @param totalBytesExpectedToWrite 文件总大小
 */


/* 执行下载任务时有数据写入 */
- (void)URLSession:(NSURLSession *)session
      downloadTask:(NSURLSessionDownloadTask *)downloadTask
      didWriteData:(int64_t)bytesWritten
 totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
    // 计算当前下载进度并更新视图
    float downloadProgress = totalBytesWritten / (float)totalBytesExpectedToWrite;
    NSLog(@"----%@", [NSThread currentThread]);

    WeakSelf;
    dispatch_async(dispatch_get_main_queue(), ^{
        /* 根据下载进度更新视图 */
        weakSelf.progressView.progress = downloadProgress;
    });
    
}


/* 从fileOffset位移处恢复下载任务 */
- (void)URLSession:(NSURLSession *)session
      downloadTask:(NSURLSessionDownloadTask *)downloadTask
 didResumeAtOffset:(int64_t)fileOffset
expectedTotalBytes:(int64_t)expectedTotalBytes {
    NSLog(@"NSURLSessionDownloadDelegate: Resume download at %lld", fileOffset);
}

/* 完成下载任务,无论下载成功还是失败都调用该方法 */
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
    
    NSLog(@"=====%@", [NSThread currentThread]);
    //恢复按钮的点击效果
   [self setDownloadButtonsWithEnabled:YES];

    if (error) {
        NSLog(@"下载失败:%@", error);
        self.progressView.progress = 0.0;
        self.imageView.image = nil;
    }
}

其中, 我们操作最多的该是下边这个代理


/* 完成下载任务,只有下载成功才调用该方法 */
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location {
    NSLog(@"NSURLSessionDownloadDelegate: Finish downloading");
    NSLog(@"----%@", [NSThread currentThread]);

    // 1.将下载成功后的文件<在tmp目录下>移动到目标路径
    NSFileManager *fileManager = [NSFileManager defaultManager];
    NSArray *fileArray = [fileManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask];
    NSURL *destinationPath = [fileArray.firstObject URLByAppendingPathComponent:[location lastPathComponent]];
    NSLog(@"----路径--%@/n---%@", destinationPath.path, location);
    if ([fileManager fileExistsAtPath:[destinationPath path] isDirectory:NULL]) {
        [fileManager removeItemAtURL:destinationPath error:NULL];
    }
    
        //在此有个下载文件后缀的问题

    
    //2将下载默认的路径移植到指定的路径
    NSError *error = nil;
    if ([fileManager moveItemAtURL:location toURL:destinationPath error:&error]) {
        self.progressView.progress = 1.0;
        //刷新视图,显示下载后的图片
        UIImage *image = [UIImage imageWithContentsOfFile:[destinationPath path]];
        WeakSelf;
        //回主线程
        dispatch_async(dispatch_get_main_queue(), ^{
            weakSelf.imageView.image = image;
        });
    }
    
    // 3.取消已经完成的下载任务
    if (downloadTask == self.cancelDownloadTask) {
        self.cancelDownloadTask = nil;
    }else if (downloadTask == self.resumableDownloadTask) {
        self.resumableDownloadTask = nil;
        self.resumableData = nil;
    }else if (session == self.backgroundSession) {
        self.backDownloadTask = nil;
        AppDelegate *appDelegate = [AppDelegate sharedDelegate];
        if (appDelegate.backgroundURLSessionCompletionHandler) {
            // 执行回调代码块
            void (^handler)() = appDelegate.backgroundURLSessionCompletionHandler;
            appDelegate.backgroundURLSessionCompletionHandler = nil;
            handler();
        }
    }
}


**请思考一下: 为何要回主线程中更新视图? **


知道了断点续传的大致原理以及下载的常用方法, 接下来就先实现断点续传

第四.实现断点续传---下载了较大的文件

苹果在 iOS7 开始,推出了一个新的类 NSURLSession, 它具备了 NSURLConnection 所具备的方法,并且更强大。2015年NSURLConnection开始被废弃了, 所以直接上NSURLSession的子类NSURLSessionDownloadTask;

// 当前会话
@property (strong, nonatomic)  NSURLSession *currentSession;  

----------------------------------------------

// 可恢复的下载任务
@property (strong, nonatomic) NSURLSessionDownloadTask *resumableTask;
// 用于可恢复的下载任务的数据
@property (strong, nonatomic) NSData *partialData;
---------------------------------------------

#pragma mark 暂停/继续下载
- (IBAction)suspendDownload:(id)sender {
    
    //在此对该按钮做判断
    if (self.judgeSuspend) {
        [self.suspendBtn setTitle:@"继续下载" forState:UIControlStateNormal];
        self.judgeSuspend = NO;
        [self suspendDown];
    }else{
         [self.suspendBtn setTitle:@"暂停下载" forState:UIControlStateNormal];
        self.judgeSuspend = YES;
        [self startResumableDown];
    }
}


以上步骤其实像数据持久化的那一层一样, 先判断本地数据, 然后在做是否从网络获取的操作.但前提是, 退出/暂停时必须将下载的数据保存起来以便后续使用.

以下展示了继续下载和暂停下载的代码:

//继续下载
- (void)startResumableDown{

    if (!self.resumableDownloadTask) {
        // 如果是之前被暂停的任务,就从已经保存的数据恢复下载
        if (self.resumableData) {
            self.resumableDownloadTask = [self.currentSession downloadTaskWithResumeData:self.resumableData];
        }else {
            // 否则创建下载任务
            NSString *imageURLStr = @"http://dlsw.baidu.com/sw-search-sp/soft/9d/25765/sogou_mac_32c_V3.2.0.1437101586.dmg";
            NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:imageURLStr]];
            self.resumableDownloadTask = [self.currentSession downloadTaskWithRequest:request];
        }
        //关闭所有的按钮的相应
        [self setDownloadButtonsWithEnabled:NO];
        self.suspendBtn.enabled   = YES;
        self.imageView.image = nil;
        [self.resumableDownloadTask resume];
    }

}


//暂停下载
- (void)suspendDown{

    if (self.resumableDownloadTask) {
        [self.resumableDownloadTask cancelByProducingResumeData:^(NSData *resumeData) {
            // 如果是可恢复的下载任务,应该先将数据保存到partialData中,注意在这里不要调用cancel方法
            self.resumableData = resumeData;
            self.resumableDownloadTask = nil;
        }];
    }

}

第五. 实现后台下载----下载较大文件

第一步, 不变的初始化

#pragma mark 后台下载
- (IBAction)backDownload:(id)sender {
        NSString *imageURLStr = @"http://dlsw.baidu.com/sw-search-sp/soft/9d/25765/sogou_mac_32c_V3.2.0.1437101586.dmg";
        NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:imageURLStr]];
        self.backDownloadTask = [self.backgroundSession downloadTaskWithRequest:request];
        [self setDownloadButtonsWithEnabled:NO];
        [self.backDownloadTask resume];
}

第二步: 对于获取数据的地方

/* 完成下载任务,只有下载成功才调用该方法 */
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location {
    NSLog(@"NSURLSessionDownloadDelegate: Finish downloading");
    NSLog(@"----%@", [NSThread currentThread]);

    // 1.将下载成功后的文件<在tmp目录下>移动到目标路径
    NSFileManager *fileManager = [NSFileManager defaultManager];
    NSArray *fileArray = [fileManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask];
    NSURL *destinationPath = [fileArray.firstObject URLByAppendingPathComponent:[location lastPathComponent]];
    
    
    //在此有个下载文件后缀的问题
    
    NSLog(@"----路径--%@/n---%@", destinationPath.path, location);
    if ([fileManager fileExistsAtPath:[destinationPath path] isDirectory:NULL]) {
        [fileManager removeItemAtURL:destinationPath error:NULL];
    }
    
    //2将下载默认的路径移植到指定的路径
    NSError *error = nil;
    if ([fileManager moveItemAtURL:location toURL:destinationPath error:&error]) {
        self.progressView.progress = 1.0;
        //刷新视图,显示下载后的图片
        UIImage *image = [UIImage imageWithContentsOfFile:[destinationPath path]];
        WeakSelf;
        //回主线程
        dispatch_async(dispatch_get_main_queue(), ^{
            weakSelf.imageView.image = image;
        });
    }
    
    [self setDownloadButtonsWithEnabled:YES];

    // 3.取消已经完成的下载任务
    if (session == self.backgroundSession) {
        self.backDownloadTask = nil;
        AppDelegate *appDelegate = [UIApplication sharedApplication].delegate;
        
        //需要在APPDelegate中做相应的处理
        if (appDelegate.backgroundURLSessionCompletionHandler) {
            // 执行回调代码块
            void (^handler)() = appDelegate.backgroundURLSessionCompletionHandler;
            appDelegate.backgroundURLSessionCompletionHandler = nil;
            handler();
        }
    
}



因为是后台处理, 因此需要在程序入口做处理

#import <UIKit/UIKit.h>

@interface AppDelegate : UIResponder <UIApplicationDelegate>

@property (strong, nonatomic) UIWindow *window;

/* 用于保存后台下载任务完成后的回调代码块 */
@property (copy) void (^backgroundURLSessionCompletionHandler)();

@end

---

#import "AppDelegate.h"

/* 后台下载任务完成后,程序被唤醒,该方法将被调用 */
- (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)())completionHandler {
    NSLog(@"Application Delegate: Background download task finished");
    
    // 设置回调的完成代码块
    self.backgroundURLSessionCompletionHandler = completionHandler;
}

Demo地址---iOSDownload


参考:
浅析 iOS 应用开发中的断点续传

更多精彩内容请关注“IT实战联盟”哦~~~


IT实战联盟.jpg
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,923评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,154评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,775评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,960评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,976评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,972评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,893评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,709评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,159评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,400评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,552评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,265评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,876评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,528评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,701评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,552评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,451评论 2 352

推荐阅读更多精彩内容