网络:封装下载方法

#import "ViewController.h"
#import "ProgressButton.h"
#import "DownloadOperation.h"
@interface ViewController ()<NSURLConnectionDataDelegate>

@property (weak, nonatomic) IBOutlet ProgressButton *progressBtn;
@property (nonatomic, strong) DownloadOperation *download; // 下载操作
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

}

// 断点续传 下一次下载,从上一次下载到的地方开始
- (IBAction)pause:(id)sender {

    [self.download pause];
}


- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    // NSURL
    NSString *URLStr =@"http://localhost/01UI基础复习.mp4";

    // 百分号转码
    URLStr = [URLStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    NSURL *url = [NSURL URLWithString:URLStr];

   self.download = [DownloadOperation downloadWithUrl:url progress:^(CGFloat progress) {
        dispatch_async(dispatch_get_main_queue(), ^{
            self.progressBtn.progress = progress;
        });
    } finish:^(NSString *targetPath, NSError *error) {
        // 回调只需要传路径就可以了。程序拿到资源的路径就可以使用了,不需要拿到资源的二进制数据
        NSLog(@"下载完成%@ -- %@",targetPath,[NSThread currentThread]);
    }];
}



@end
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@interface DownloadOperation : NSObject
/**
 url 要下载的URL
 progress 进度回调
 finish 完成回调
 */
+ (instancetype)downloadWithUrl:(NSURL *)url progress:(void(^)(CGFloat progress))progress finish:(void(^)(NSString *targetPath,NSError *error))finish;

/// 暂停下载
- (void)pause;
@end












#import "DownloadOperation.h"

@interface DownloadOperation ()<NSURLConnectionDataDelegate>
@property (nonatomic, copy) void (^progress)(CGFloat);
@property (nonatomic, copy) void (^finish)(NSString *, NSError *);

@property (nonatomic, assign) long long fileSize; // 文件总大小
@property (nonatomic, assign) long long currentLocalSize; // 本地文件的大小
@property (nonatomic, assign) long long currentSize; // 当前接收的文件大小
@property (nonatomic, strong) NSOutputStream *output; // 文件输出流
@property (nonatomic, strong) NSURLConnection *connection; // 下载请求连接
@property (nonatomic, copy) NSString *filePath; // 文件保存的路径
@property (nonatomic, strong) NSURL *url;

@end

@implementation DownloadOperation
+ (instancetype)downloadWithUrl:(NSURL *)url progress:(void (^)(CGFloat))progress finish:(void (^)(NSString *, NSError *))finish {
    DownloadOperation *download = [[self alloc]init];

    download.progress = progress;
    download.finish = finish;
    download.url = url;
    // 开始下载
    [download startDownload];
    return download;
}

- (void)pause {
    [self.connection cancel];
}

// HEAD用来请求查看文件大小
- (void)startDownload {

    NSURL *url = self.url;
    // NSURLRequest 获取文件大小,不是使用GET,而是使用HEAD
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    [request setHTTPMethod:@"HEAD"];

    NSHTTPURLResponse *response;
    // 获取文件大小,是使用同步
    [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:NULL];

    // 建议保存的文件名
    //        NSLog(@"%@",response.suggestedFilename);
    self.filePath = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:response.suggestedFilename];
    self.fileSize = response.expectedContentLength;
//    NSLog(@"%@",self.filePath);
    //    NSLog(@"%lld",fileSize);
    // 提示用户文件总大下,是否需要下载

    // 检查本地文件大小
    self.currentLocalSize = [self checkLocalFile];
    self.currentSize = self.currentLocalSize;
    if (self.currentLocalSize == self.fileSize) {
//        NSLog(@"下载成功");
        if (self.progress) {
            self.progress(1);
        }
        // 需要回调
        if (self.finish) {
            self.finish(self.filePath,nil);
        }
//        dispatch_async(dispatch_get_main_queue(), ^{
//           
//        });
        return;
    }
    // 断点续传 0 , 100000

    // 下载文件
    [self download:url];
}

- (long long)checkLocalFile {
    long long fileSize = 0;
    // 下载之前先判断本地文件跟服务器文件之前的关系
    NSFileManager *manager = [NSFileManager defaultManager];
    // 文件属性
    NSDictionary *att = [manager attributesOfItemAtPath:self.filePath error:NULL];
    // 全到本地文件的大小
    long long localFileSize = att.fileSize;
    if (localFileSize > self.fileSize) { // 删除本地文件
        // 删除
        [manager removeItemAtPath:self.filePath error:NULL];
        fileSize = 0;
    }else if (localFileSize == self.fileSize) {
        // 不需要重新下载
        fileSize = localFileSize;
    }else { // 本地文件小于服务器文件
        fileSize = localFileSize;
    }

    return fileSize;

}

// 断点续传第三方框架 很少有第三方框架去实现
// ASIHTTP(好几年没更新)
- (void)download:(NSURL *)url {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        // NSURLRequest 下载文件,从服务器获取的意思 GET
        NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];

        NSString *range = [NSString stringWithFormat:@"bytes=%lld-",self.currentLocalSize];
        // 设置Range头
        [request setValue:range forHTTPHeaderField:@"Range"];

        // 开始下载文件, 知道下载的进度
        // 代理回调的线程,跟执行这一行代码的线程是同一个
        self.connection = [NSURLConnection connectionWithRequest:request delegate:self];
        //        NSLog(@"开始下载文件");
        // 开启运行循环,才能让子线程保持
        // 什么时候需要开启运行循环
        /**
         1. 子线程需要保持,NSTimer
         2. 代理回调(代理,block)
         */
        [[NSRunLoop currentRunLoop] run];
    });

}

#pragma mark - NSURLConnection 代理
/**
 NSFileHandle 选择写入文件的方式初始化,在写入文件之前先把光标移动文件的最后,写完之后关闭
 NSOutputStream 初始化的时候选择拼接文件,再打开流,写入数据(多次),关闭流

 */
// 接收到响应
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
    // 如果使用了Range头,响应的状态码是206
    NSLog(@"接收到响应%@ -- %lld",response,response.expectedContentLength);
    //    NSHTTPURLResponse *httpResp
    //    self.fileSize = response.expectedContentLength; // 文件总大小

    self.output = [[NSOutputStream alloc]initToFileAtPath:self.filePath append:YES];
    // 在写入编辑文件之前,打开文件
    [self.output open];

}

// 如果代理方法在主线程中执行
/**
 1. 方法会调用很多次
 2. 如果主线程没空,不会调用代理(视力滚动的时候,或者在做其他事情),也就是相当于不下载
 */
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
    //    NSLog(@"接收到数据 %zd",data.length);
    // 如果需要知道进度,首要要知道文件的总大小,还要接收了多少
    self.currentSize += data.length;
    CGFloat progress = (CGFloat)self.currentSize / self.fileSize;
//    NSLog(@"%f", progress);
    // 设置进度视图,如果文件比较小,就不需要进行条
    // 我们通过子线程回调,用户拿到之后,需不需要刷新由他决定
    if (self.progress) {
        self.progress(progress);
    }

    // uunt8_t -> NSData
    //    [NSData dataWithBytes:<#(nullable const void *)#> length:<#(NSUInteger)#>]
    [self.output write:data.bytes maxLength:data.length];
    // 在真实开发不使用,会挂起运行循环
//    [NSThread sleepForTimeInterval:0.1];
//    NSLog(@"%@",[NSThread currentThread]);

}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
    NSLog(@"下载完成了");
    dispatch_async(dispatch_get_main_queue(), ^{
        if (self.finish) {
            self.finish(self.filePath,nil);
        }
    });
        // 关闭文件流
    [self.output close];
}

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
    NSLog(@"出错了");
    dispatch_async(dispatch_get_main_queue(), ^{
        if (self.finish) {
            self.finish(nil,error);
        }
    });
}

@end
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值