欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

iOS进阶_下载管理器(封装下载用工具类)

程序员文章站 2022-05-19 15:54:08
...

同步方法的应用场景:

1.抓取网络数据,如果开启多条线程异步抓取,很容易被封IP
2.加载本地文件,可以直接使用同步方法,比较简单
3. 加载要下载文件的头部信息,HEAD方法

-(void)demo1{

    NSURL * url = [NSURL URLWithString:@"http://127.0.0.1/abc.json"];
    NSURLRequest * request = [NSURLRequest requestWithURL:url];

    /**
     同步方法是一个阻塞的!
     参数:
     1.reques 请求
     2.Response    *__autoreleasing * 是服务器返回的响应的地址!
     3.错误的地址!
     */
    NSURLResponse * response = nil;
    NSData * data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:NULL];
    //反序列化
    id result = [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL];
    NSLog(@"%@ %@",result,response);

}

拓展: __autoreleasing &response

(**表示) 指针的指针
在 C/OC/C++ 中,指针的指针通常使用来在一个方法中返回多个数值!!
今后看到参数是 ** 就是一个指向对象的指针!!!

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

    NSString * name = nil;
    int age = 10;
    int userID =  [self userIDWithAge:&age title:&name];
    NSLog(@"userID:%d  Age:%d  name:%@",userID,age,name);
}

-(int)userIDWithAge:(int *)age title:(NSString **)title {
    *title = [NSString stringWithFormat:@"wt"];
    *age = 99;
    return 1;
}

nil和NULL的区别:
nil :是地址指向NULL的空对象,在OC/C++中,给nil对象发送消息,不起任何作用
NULL:是空地址,本身就是0,就是一个整数,不能给NULL发送消息

什么是安全释放!!
[objc release]; 释放之后,不会修改对象的指针地址.
后续如果继续给objc 发送消息,就会出现野指针错误!!
objc = nil;//对象地址已经指向了 NULL(0),这个时候再发消息,都不会报错!!

下载管理器的实现思路

这才是正式的文章,上面的是餐前甜点。。。
这次我们自己来封装一个做下载用的工具类
目的 -> 下载
1.先实现一个简单的下载功能
2.对外提供接口

NSURLSession下载
1.跟踪进度
2.断点续传,问题:这个resumeData丢失,再次下载的时候,无法续传!!
考虑解决方案:
- 将文件保存在固定的位置
- 再次下载文件前,先检查固定位置是否存在文件
- 如果有,直接续传!!!

iOS进阶_下载管理器(封装下载用工具类)

封装下载工具类

WTDownloader.h

#import <Foundation/Foundation.h>

@interface WTDownloader : NSObject

/**
 *  下载指定url的文件
 *
 *  @param url 要下载的url
 */
-(void)downloadWithURL:(NSURL *)url;

@end

WTDownloader.m

#import "WTDownloader.h"



#define kTimeOut 20.0

@interface WTDownloader ()<NSURLConnectionDataDelegate>
/** 文件输出流  */
@property(nonatomic,strong)NSOutputStream * fileStream;

/** 网络文件总大小 */
@property(assign,nonatomic)long long  expectedContentLength;
/** 本地文件总大小 */
@property(assign,nonatomic)long long currentLength;
/** 文件路径 */
@property(copy,nonatomic)NSString * filePath;
/** 下载文件的URL  */
@property(nonatomic,strong)NSURL * downloadURL;
/** 下载的Runloop */
@property(assign,nonatomic)CFRunLoopRef downloadRunloop;
//--------------BLOCK属性---------------
@property(copy,nonatomic)void(^progressBlock)(float);
@property(copy,nonatomic)void(^completionBlock)(NSString *);
@property(copy,nonatomic)void(^failedBlock)(NSString *);


@end

/**
 NSURLSession下载
 1.跟踪进度
 2.断点续传,问题:这个resumeData丢失,再次下载的时候,无法续传!!
    考虑解决方案:
        - 将文件保存在固定的位置
        - 再次下载文件前,先检查固定位置是否存在文件
        - 如果有,直接续传!!!

 */

@implementation WTDownloader


/**
 很多三方框架有一个共同特点(SDWebImage/AFN/ASI)
 进度的回调,是在异步线程回调的 
        -- 因为进度回调会调用多次,如果在主线程,会影响UI交互!!
 完成之后的回调,在主线程
        -- 通常调用方不需要关心线程间的通讯,一旦完成直接更新UI更方便

 */

-(void)downloadWithURL:(NSURL *)url Progress:(void (^)(float))progress completion:(void (^)(NSString *))completion failed:(void (^)(NSString *))failed
{
    //0.保存属性
    self.downloadURL = url;
    self.progressBlock = progress;
    self.completionBlock = completion;
    self.failedBlock = failed;


    //1.检查服务器上的文件大小!
    [self serverFileInfoWithURL:url];

    NSLog(@"%lld  %@",self.expectedContentLength,self.filePath);

    //2.检查本地文件大小!
    if(![self checkLocalFileInfo]){
        NSLog(@"文件已经下载完毕了!!");
        return;
    };

    //3.如果需要,从服务器开始下载!
    NSLog(@"从我们的%lld下载文件",self.currentLength);
    [self downloadFile];
}


#pragma mark - <下载文件>
//从 self.currentLength 开始下载文件
-(void)downloadFile{
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        //1.建立请求
        NSMutableURLRequest * request = [NSMutableURLRequest requestWithURL:self.downloadURL cachePolicy:1 timeoutInterval:kTimeOut];
        //设置下载的字节范围 从 self.currentLength 开始之后所有的字节
        NSString * rangeStr = [NSString stringWithFormat:@"bytes=%lld-",self.currentLength];
        //设置请求头字段
        [request setValue:rangeStr forHTTPHeaderField:@"Range"];

        //2.开始网络连接
        NSURLConnection * conn = [NSURLConnection connectionWithRequest:request delegate:self];
        //3.启动完了连接
        [conn start];

        //4.利用运行循环实现多线程不被回收
        self.downloadRunloop = CFRunLoopGetCurrent();
        CFRunLoopRun();
    });
}


#pragma mark - <私有方法>
/**
 *  检查本地文件信息 --> 判断是否需要下载
 *
 *  @return YES 需要下载, NO 不需要下载
 */
-(BOOL)checkLocalFileInfo{
    long long fileSize = 0;

    //1.文件是否存在
    if([[NSFileManager defaultManager] fileExistsAtPath:self.filePath]){
        //2.获取文件大小
        NSDictionary * attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:self.filePath error:NULL];
        //fileSize = [attributes[NSFileSize] longLongValue];
        //利用分类方法获取文件大小
        fileSize = [attributes fileSize];
    }

    /*
     如果大小小于服务器的大小,从本地文件的长度开始下载!!!(续传)
     如果大小等于服务器的大小,认为文件已经下载完毕
     如果大小大于服务器的大小,直接干掉,重新下载
     */
    //大于服务器的文件
    if (fileSize > self.expectedContentLength) {
        //删除这个文件
        [[NSFileManager defaultManager] removeItemAtPath:self.filePath error:NULL];
        fileSize = 0;
    }
    //是否文件和服务器的文件大小一样
    self.currentLength = fileSize;
    if (fileSize == self.expectedContentLength) {
        if (self.completionBlock) {
            self.completionBlock(self.filePath);
        }
        return NO;
    }

    return YES;
}



//检查服务器文件大小
-(void)serverFileInfoWithURL:(NSURL *)url{
    //1.请求
    NSMutableURLRequest * request = [NSMutableURLRequest requestWithURL:url cachePolicy:1 timeoutInterval:kTimeOut];
    request.HTTPMethod = @"HEAD";
    //2.建立网络连接
    NSURLResponse * response = nil;
    [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:NULL];
    //3.记录服务器的文件信息
    //3.1 文件长度
    self.expectedContentLength = response.expectedContentLength;
    //3.2 建议保存的文件名,将在的文件保存在tmp ,系统会自动回收
    self.filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:response.suggestedFilename];
    return;
}


#pragma mark - <NSURLConnectionDataDelegate>
//1.接收到服务器的响应
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
    //打开输出流
    self.fileStream = [[NSOutputStream alloc] initToFileAtPath:self.filePath append:YES];
    [self.fileStream open];
}

//2.接收到数据,用输出流拼接,计算下载进度
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
    //追加数据
    [self.fileStream write:data.bytes maxLength:data.length];
    //记录文件的长度
    self.currentLength += data.length;

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

    //判断block是否存在
    if (self.progressBlock) {
        self.progressBlock(progress);
    }


}

//3.所有下载完毕
-(void)connectionDidFinishLoading:(NSURLConnection *)connection
{
    //关闭流
    [self.fileStream close];
    //停止运行循环
    CFRunLoopStop(self.downloadRunloop);
    //判断BLock是否存在
    if (self.completionBlock) {
        //主线程回调
        dispatch_async(dispatch_get_main_queue(), ^{self.completionBlock(self.filePath); });
    }

}

//4.出错
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
    //关闭流
    [self.fileStream close];
    //停止运行循环
    CFRunLoopStop(self.downloadRunloop);
    //判断BLock是否存在
    if (self.failedBlock) {
        self.failedBlock(error.localizedDescription);
    }
    NSLog(@"%@",error.localizedDescription);

@end

工具类调用

#import "ViewController.h"
#import "WTDownloader.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    WTDownloader * downloader = [[WTDownloader alloc]init];
    NSURL * url = [NSURL URLWithString:@"http://127.0.0.1/abc.wmv"];
    [downloader downloadWithURL:url Progress:^(float progress) {
        NSLog(@"--->%f  %@",progress,[NSThread currentThread]);
    } completion:^(NSString *filePath) {
        //下载成功了
        NSLog(@"下载完成了 %@ %@",filePath,[NSThread currentThread]);

    } failed:^(NSString *errorMsg) {
        NSLog(@"下载失败了:%@",errorMsg);
    }];
}

@end
相关标签: 下载管理器