iOS Developer - Network工具iOS常用功能和UI效果的实现

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

2016-08-05  本文已影响366人  simuty

目录

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:https://img.haomeiwen.com/i326255/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
上一篇下一篇

猜你喜欢

热点阅读