当前位置:   article > 正文

iOS进阶_下载管理器(封装下载用工具类)_ios 文件下载器设计

ios 文件下载器设计

同步方法的应用场景:

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);

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

拓展: __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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

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

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

下载管理器的实现思路

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

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

这里写图片描述

封装下载工具类

WTDownloader.h

#import <Foundation/Foundation.h>

@interface WTDownloader : NSObject

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

@end
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217

工具类调用

#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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Li_阴宅/article/detail/813227
推荐阅读
相关标签
  

闽ICP备14008679号