• iOS中的下载管理器(支持断点续传)


    在空闲时间自己编写了一个简单的iOS下载管理器。该管理器实现如下功能:

      1、能够支持正常的下载,暂停,继续操作。

      2、支持断点续传,实现暂停执行继续操作后,依然能正常将文件下载完成。

      3、实现实时状态回调,下载进度,速度,一目了然。

    准备工作:压缩文件

    遇到的主要问题: 拼接到内存中的数据峰值太大,会导致app闪退.

    解决办法:

    一.(1)用NSFileHandle解决占用内存过大问题(下载一点 写入沙盒一点)

    #import "ViewController.h"

    @interface ViewController ()<NSURLConnectionDataDelegate>

    @property(nonatomic,copy)NSString *filePath;

    /**

     *  服务器上面的文件的总大小

     */

    @property(nonatomic,assign)long long expectedContentLength;

    /**

     *  已经接收到的文件的大小

     */

    @property(nonatomic,assign)long  long hasReceivedContentLength;

    /**

     *  文件的管道

     */

    @property(nonatomic,strong)NSFileHandle *writeFileHandle;

    @end

    @implementation ViewController

     #pragma mark - 懒加载

    - (NSString *)filePath{

        if (_filePath==nil) {

            //获取到Cache目录

           NSString *cache = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];

            

            _filePath = [cache stringByAppendingPathComponent:@"dh.zip"];

        }

        

        return _filePath;

    }

    - (void)viewDidLoad {

        [super viewDidLoad];

        // Do any additional setup after loading the view, typically from a nib.

        NSLog(@"%@",NSHomeDirectory());

    }

    /**

        这个地方不太好

         1.没有进度

        2.进度峰值太大,会导致我们的app闪退

     */

    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{

        //1.URL

        NSURL *url = [NSURL URLWithString:@"http://localhost/video.zip"];

        

        //2.NSURLRequest

        NSURLRequest *request = [NSURLRequest requestWithURL:url];

        

        //3.只要调用了类方法就会自动发起请求

       NSURLConnection *connection =  [NSURLConnection connectionWithRequest:request delegate:self];

    }

    #pragma mark - DownLoadDelegate

    /*

        这个只会调用一次

     */

    - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{

        NSLog(@"下载开始!!!");

        self.expectedContentLength = response.expectedContentLength;

        

        //1.创建一个0KB的文件,空文件

        /**

            NSFileManger   做大事,创建,删除,移动我们的文件,并且还可以获取文件的信息

            NSFileHandle   做小事,专门用来,流入数据

         */

        NSFileManager *manager = [NSFileManager defaultManager];

        BOOL createSuccess =  [manager createFileAtPath:self.filePath contents:nil attributes:NULL];

        

        //2.创建管道

        if (createSuccess) {

            NSLog(@"创建文件成功!!!");

            self.writeFileHandle = [NSFileHandle fileHandleForWritingAtPath:self.filePath];

        }

    }

    /**

        这个玩意儿会调用多次

     */

    - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{

        //NSLog(@"%zd",data.length);

        //1.拼接文件的总大小

        self.hasReceivedContentLength += data.length;

        

        float progress = (float)self.hasReceivedContentLength / (float)self.expectedContentLength;

        NSLog(@"progress==%f",progress);

        

        //2.接收到一点,就写入到沙盒指定的文件里面去一点

        //2.1把文件指针,移到已经下载完毕的文件的末尾

        [self.writeFileHandle seekToEndOfFile];

        

        //2.2 将新获取到的data 拼接到已经下载完毕的文件的末尾

        [self.writeFileHandle writeData:data];

    }

    /**

        只会调用一次

     */

    - (void)connectionDidFinishLoading:(NSURLConnection *)connection{

        NSLog(@"下载完毕!!!");

            //需要将管道关闭

        [self.writeFileHandle closeFile];

    }

     @end

    (2)用NSOutputStream解决占用内存过大问题

    #import "ViewController.h"

    @interface ViewController ()<NSURLConnectionDataDelegate>

    @property(nonatomic,copy)NSString *filePath;

    /**

     *  服务器上面的文件的总大小

     */

    @property(nonatomic,assign)long long expectedContentLength;

    /**

     *  已经接收到的文件的大小

     */

    @property(nonatomic,assign)long  long hasReceivedContentLength;

    /**

     *  输出,输入是以内存为参照物的

        输出,就是将内存中的东西,输出到沙盒

        输入,就是将沙盒中的东西输入到内存

     */

    @property(nonatomic,strong)NSOutputStream *outputStream;

    @end

    @implementation ViewController

    #pragma mark - 懒加载

    - (NSString *)filePath{

        if (_filePath==nil) {

            //获取到Cache目录

           NSString *cache = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];

            

            _filePath = [cache stringByAppendingPathComponent:@"dh.zip"];

        }

        

        return _filePath;

    }

    - (void)viewDidLoad {

        [super viewDidLoad];

        // Do any additional setup after loading the view, typically from a nib.

        NSLog(@"%@",NSHomeDirectory());

    }

    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{

        NSLog(@"%s",__func__);

        dispatch_async(dispatch_get_global_queue(0, 0), ^{

            //1.URL

            NSURL *url = [NSURL URLWithString:@"http://localhost/video.zip"];

            

            //2.NSURLRequest

            NSURLRequest *request = [NSURLRequest requestWithURL:url];

            

            //3.只要调用了类方法就会自动发起请求

            NSURLConnection *connection =  [NSURLConnection connectionWithRequest:request delegate:self];

            

            //开启子线程的NSRunloop,下载比较特殊,只要下载完毕之后,他的NSRunloop会自动停止

            [[NSRunLoop currentRunLoop] run];

        });

    }

    #pragma mark - DownLoadDelegate

    /*

        这个只会调用一次

     */

    - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{

        NSLog(@"下载开始!!!");

        self.expectedContentLength = response.expectedContentLength;

        

        //1.创建输出流

        self.outputStream = [NSOutputStream outputStreamToFileAtPath:self.filePath append:YES];

        

        //2.打开流

    #warning 打开流

        [self.outputStream open];

    }

    /**

        这个玩意儿会调用多次

     */

    - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{

        //NSLog(@"%zd",data.length);

        //1.拼接文件的总大小

        self.hasReceivedContentLength += data.length;

        

        float progress = (float)self.hasReceivedContentLength / (float)self.expectedContentLength;

        NSLog(@"progress==%f %@",progress,[NSThread currentThread]);

        

        //获取一点,就写入沙盒一点

        [self.outputStream write:data.bytes maxLength:data.length];

    }

    /**

        只会调用一次

     */

    - (void)connectionDidFinishLoading:(NSURLConnection *)connection{

        NSLog(@"下载完毕!!!");

        

        //关闭流

        [self.outputStream close];

    }

    @end

    二.文件下载进度(暂停和恢复)

    #import "ViewController.h"

    @interface ViewController ()<NSURLConnectionDataDelegate>

    @property(nonatomic,copy)NSString *filePath;

    /**

     *  服务器上面的文件的总大小

     */

    @property(nonatomic,assign)long long expectedContentLength;

    /**

     *  已经接收到的文件的大小

     */

    @property(nonatomic,assign)long  long hasReceivedContentLength;

    /**

     *  输出,输入是以内存为参照物的

        输出,就是将内存中的东西,输出到沙盒

        输入,就是将沙盒中的东西输入到内存

     */

    @property(nonatomic,strong)NSOutputStream *outputStream;

    /**

     *  connection

     */

    @property(nonatomic,strong)NSURLConnection *connection;

    @end

    @implementation ViewController

    #pragma mark - 懒加载

    - (NSString *)filePath{

        if (_filePath==nil) {

            //获取到Cache目录

           NSString *cache = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];

            

            _filePath = [cache stringByAppendingPathComponent:@"dh.zip"];

        }

        

        return _filePath;

    }

    - (void)viewDidLoad {

        [super viewDidLoad];

        

        NSLog(@"%@",NSHomeDirectory());

    }

    //开始下载

    - (IBAction)start:(id)sender {

        NSLog(@"%s",__func__);

        dispatch_async(dispatch_get_global_queue(0, 0), ^{

            //获取到我们的文件的总大小

            [self getFileLenght];

            

            //1.URL

            NSURL *url = [NSURL URLWithString:@"http://localhost/video.zip"];

            

            //2.NSURLRequest

            NSURLRequest *request = [NSURLRequest requestWithURL:url];

            

            //3.只要调用了类方法就会自动发起请求

            self.connection =  [NSURLConnection connectionWithRequest:request delegate:self];

            

            //开启我们子线程的NSRunloop,下载比较特殊,只要下载完毕之后,他的NSRunloop会自动停止

            [[NSRunLoop currentRunLoop] run];

        });

    }

    //暂停

    - (IBAction)pause:(id)sender {

        

        //取消

        //一旦调用了这个方法,connnection就废了

        [self.connection cancel];

    }

    //恢复

    - (IBAction)resume:(id)sender {

        dispatch_async(dispatch_get_global_queue(0, 0), ^{

            //先得到文件的大小

            [self getFileLenght];

            

            //1.URL

            NSURL *url = [NSURL URLWithString:@"http://localhost/video.zip"];

            

            //2.NSURLRequest

            NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];

            //2.1 设置请求头,告诉服务器一些额外的信息

            NSString *rangeValue = [NSString stringWithFormat:@"bytes=%lld-",self.hasReceivedContentLength];

            [request setValue:rangeValue forHTTPHeaderField:@"Range"];

            

            //3.只要调用了类方法就会自动发起请求

            self.connection =  [NSURLConnection connectionWithRequest:request delegate:self];

            

            //开启我们子线程的NSRunloop,下载比较特殊,只要下载完毕之后,他的NSRunloop会自动停止

            [[NSRunLoop currentRunLoop] run];

        });

    }

    - (void)getFileLenght{

        //1.URL

        NSURL *url = [NSURL URLWithString:@"http://localhost/video.zip"];

        

        //2.NSURLRequest

        NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];

        //如果只是获取文件的信息,而不是把数据download下来

        request.HTTPMethod = @"HEAD";

        

        //3.发送请求去服务器上面,获取文件的信息

        NSURLResponse *response;

        NSData *data =[NSURLConnection sendSynchronousRequest:request returningResponse:&response error:NULL];

        NSLog(@"getFileLenght %zd",data.length);

        

        //4.设置给我们文件的总长度

        self.expectedContentLength = response.expectedContentLength;

        NSLog(@"文件的总大小 %zd",self.expectedContentLength);

    }

    #pragma mark - DownLoadDelegate

    /*

        这个只会调用一次

     */

    - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{

        NSLog(@"下载开始!!!");

        

        NSLog(@"文件总大小----------%lld",response.expectedContentLength);

        

        //1.创建输出流

        self.outputStream = [NSOutputStream outputStreamToFileAtPath:self.filePath append:YES];

        

        //2.打开流

    #warning 打开流

        [self.outputStream open];

    }

    /**

        这个玩意儿会调用多次

     */

    - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{

        //NSLog(@"%zd",data.length);

        //1.拼接文件的总大小

        self.hasReceivedContentLength += data.length;

        

        float progress = (float)self.hasReceivedContentLength / (float)self.expectedContentLength;

        NSLog(@"progress==%f %@",progress,[NSThread currentThread]);

        

        //获取一点,就写入沙盒一点

        [self.outputStream write:data.bytes maxLength:data.length];

    }

    /**

        只会调用一次

     */

    - (void)connectionDidFinishLoading:(NSURLConnection *)connection{

        NSLog(@"下载完毕!!!");

            //关闭流

        [self.outputStream close];

    }

    @end

    三.封装进度条控件

     .h文件

    /**

        XCode6开始,可以动态的在我们Xib里面调试

     */

    #import <UIKit/UIKit.h>

    IB_DESIGNABLE

    @interface ProgressView : UIView

     /**

        进度必须在0到1之间

     */

    @property(nonatomic,assign)  IBInspectable float progress;

    /**

     *  外环

     */

    @property(nonatomic,assign) IBInspectable CGFloat waiHuanLineWidth;

    @property(nonatomic,strong) IBInspectable UIColor *huanColor;

    @end

     .m文件

    #import "ProgressView.h"

     @implementation ProgressView

    - (void)setProgress:(float)progress{

        _progress = progress;

        

        [self setNeedsDisplay];

    }

     - (void)drawRect:(CGRect)rect {

        //得到宽度和高度

        CGFloat width = rect.size.width;

        CGFloat height = rect.size.height;

        

        //1.中点

        CGPoint centerPoint = CGPointMake(width *0.5, height*0.5);

        

        //2.外环

        //7.线框

        if (self.waiHuanLineWidth==0) {

            self.waiHuanLineWidth = 5;

        }

        

        //3.半径

        CGFloat waiHuanRadius = width * 0.5 - self.waiHuanLineWidth*0.5;

        

        //4.画圆

        UIBezierPath *waiHuanBezierPath = [UIBezierPath bezierPathWithArcCenter:centerPoint radius:waiHuanRadius startAngle:0 endAngle:M_PI*2 clockwise:YES];

        

        //5.颜色

        if (self.huanColor == nil) {

            self.huanColor = [UIColor blueColor];

        }

        

        //6.设置颜色

        [self.huanColor set];

        

        //7.设置外环的宽度

        waiHuanBezierPath.lineWidth = self.waiHuanLineWidth;

        

        //8.画出来

        [waiHuanBezierPath stroke];

        

        

        //画矩形

        CGFloat cubeWidth = 20;

        CGFloat cubeHeight = 20;

        CGFloat cubeX = width *0.5 - cubeWidth*0.5;

        CGFloat cubeY = height *0.5 - cubeHeight*0.5;

        UIBezierPath *cubeBezierPath  = [UIBezierPath bezierPathWithRect:CGRectMake(cubeX, cubeY, cubeWidth, cubeHeight)];

        

        [self.huanColor set];

        

        //画出来我们中间的矩形

        [cubeBezierPath fill];

        

        //画那个动的圆形

        CGFloat neiYuanWidth = self.waiHuanLineWidth;

        

        CGFloat neiYuanRadius = width *0.5 - self.waiHuanLineWidth - neiYuanWidth*0.5;

        

        CGFloat endAngle= M_PI * 2 * self.progress + (- M_PI_2);

        

        UIBezierPath *neiYuanBezierPath = [UIBezierPath bezierPathWithArcCenter:centerPoint radius:neiYuanRadius startAngle:- M_PI_2 endAngle:endAngle clockwise:YES];

        

        [self.huanColor set];

        

        //设置线框

        neiYuanBezierPath.lineWidth = neiYuanWidth;

        

        //最后画出来

        [neiYuanBezierPath stroke];

    }

    @end

    四.由于时间关系做的不太精致,还有待改善,感兴趣的小伙伴们可以对其进行封装,使其更加的完善!

  • 相关阅读:
    1103: [POI2007]大都市meg
    bzoj2809: [Apio2012]dispatching
    bzoj3668: [Noi2014]起床困难综合症
    bzoj4025: 二分图
    bzoj4027: [HEOI2015]兔子与樱花
    bzoj3155: Preprefix sum
    http状态码status
    js改变触发
    eq
    error_reporting()
  • 原文地址:https://www.cnblogs.com/donghaoios/p/5102715.html
Copyright © 2020-2023  润新知