下载文件思路

下载文件


直接请求获取:

  • 这种方式会将数据全部接收回来,然后一次性存储到文件中,会出现内存峰值问题,也没有进度跟进
//ViewController.h

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController

@end
//ViewController.h

#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
}

/*
 1、会有内存峰值。
 2、没有进度跟进
 */
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    // 下载文件的URL
    NSString *URLString = @"http://192.168.2.23/12设置数据和frame.mp4";
    // 百分号编码(如果使用get方法请求的 url 字符串中,包含中文或空格等特殊字符,需要添加百分号转义)
    URLString = [URLString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    // URL
    NSURL *url = [NSURL URLWithString:URLString];

    // Request
    NSURLRequest *request = [NSURLRequest requestWithURL:url];

    // 发送请求-->异步下载
    // 这个方法会将数据从网络接收过来之后,在内存中拼接,再执行block中的文件存储,不能解决出现内存峰值问题.
    [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse * _Nullable response, NSData * _Nullable data, NSError * _Nullable connectionError) {

        //会将数据先缓存到内存中,然后再写入到文件中
        NSLog(@"%@---%zd",response,data.length);
        // 将文件数据写到文件中
        [data writeToFile:@"/Users/shenzhenios/Desktop/abc.mp4" atomically:YES];
    }];
@end

代理方法简单版:

  • 使用代理方法的方式(简单版)
  • 这里是一个简单版本,实现了下载进度跟进,但是还没有解决内存峰值问题.
//ViewController.h

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController

@end
//ViewController.m

#import "ViewController.h"
/*
注意: <NSURLConnectionDownloadDelegate>是错误的代理方法,里面的代理方法也可以显示下载进度,但是下载文件无
法找到,一般用在NSURLConnectionDownloadDelegate代理是使用在Newsstand Kit’s创建的NSURLConnection对象上

注:Newsstand Kit’s 是用来下载报刊,电子杂志等资料使用的框架
*/

//正确的代理
@interface ViewController () <NSURLConnectionDataDelegate>
/**
 *  要下载文件的总大小
 */
@property (nonatomic, assign) long long expectedContentLength;
/**
 *  当前已经接收文件的大小
 */
@property (nonatomic, assign) long long currentFileSize;
/**
 *  用来拼接文件数据
 */
@property (nonatomic, strong) NSMutableData *fileData;
/**
 *  保存下载文件的路径
 */
@property (nonatomic, copy) NSString *destPath;
@end

@implementation ViewController

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

/*
 1、会有内存峰值。
 */
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    // 下载文件的URL
    NSString *URLString = @"http://192.168.2.23/12设置数据和frame.mp4";
    // 百分号编码(如果使用get方法请求的 url 字符串中,包含中文或空格等特殊字符,需要添加百分号转义)
    URLString = [URLString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    // URL
    NSURL *url = [NSURL URLWithString:URLString];

    // Request
    NSURLRequest *request = [NSURLRequest requestWithURL:url];

    // 创建一个URLConnection对象,并立即加载url指定的数据,并指明了代理.
    [NSURLConnection connectionWithRequest:request delegate:self];
}

#pragma mark - NSURLConnectionDataDelegate 代理方法
/**
 *  接收到服务器响应时调用(调用一次)
 */
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
    // 获得要下载文件的大小
    self.expectedContentLength = response.expectedContentLength;

    // 获得服务器建议保存的文件名
    self.destPath = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:response.suggestedFilename];
    NSLog(@"%@", self.destPath);
}
/**
 *  接收到服务器返回的数据就调用 (有可能会调用多次)
 */
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
    // 累加文件大小
    self.currentFileSize += data.length;
    // 计算进度值
    CGFloat progress = (CGFloat)self.currentFileSize / self.expectedContentLength;

    // 将数据拼接起来
    [self.fileData appendData:data];
    NSLog(@"progress  =%f", progress);
}

/**
 *  请求完毕之后调用(调用一次)
 */
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
    NSLog(@"%s", __FUNCTION__);
    // 将文件数据写入沙盒
    [self.fileData writeToFile:self.destPath atomically:YES];

    // 清空数据
    self.fileData = nil;
}

/**
 *  请求失败/出错时调用 (一定要对错误进行处理)
 */
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
    NSLog(@"%s", __FUNCTION__);
    // 清空数据
    self.fileData = nil;
}

#pragma mark - 懒加载数据
- (NSMutableData *)fileData {
    if (_fileData == nil) {
        _fileData = [[NSMutableData alloc] init];
    }
    return _fileData;
}

@end

代理方法简单版的改进:

  • 利用NSFileHandle拼接文件,实现一块一块的写入数据,解决内存峰值问题.
  • 还存在问题就是该代码反复执行多次,文件会不断累加,不断变大.
//ViewController.h

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController

@end
//ViewController.m

#import "ViewController.h"

@interface ViewController () <NSURLConnectionDataDelegate>
/**
 *  要下载文件的总大小
 */
@property (nonatomic, assign) long long expectedContentLength;
/**
 *  当前已经接收文件的大小
 */
@property (nonatomic, assign) long long currentFileSize;
/**
 *  保存下载文件的路径
 */
@property (nonatomic, copy) NSString *destPath;
/**
 *  文件指针
 */
@property (nonatomic, strong) NSFileHandle *fp;
@end

@implementation ViewController

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

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    // 下载文件的URL
    NSString *URLString = @"http://192.168.30.79/117文件操作之字符串与二进制";
    // 百分号编码
    URLString = [URLString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    // URL
    NSURL *url = [NSURL URLWithString:URLString];

    // Request
    NSURLRequest *request = [NSURLRequest requestWithURL:url];

    // 创建一个URLConnection对象,并立即加载url执行的数据
    [NSURLConnection connectionWithRequest:request delegate:self];
}

/**
 NSFileHandle:Handle(句柄/文件指针) 一般是对Handle前一单词对象的处理,利用NSFileHandle可以对文件进行读写操作
 NSFileManager: 管理文件,检查文件是否存在,复制文件,查看文件属性...NSFileManager类似Finder
 */

#pragma mark - NSURLConnectionDataDelegate 代理方法
/**
 *  接收到服务器响应时调用
 */
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
    // 获得要下载文件的大小
    self.expectedContentLength = response.expectedContentLength;

    // 获得服务器建议保存的文件名
    self.destPath = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:response.suggestedFilename];
    NSLog(@"%@", self.destPath);
}
/**
 *  接收到服务器返回的数据就调用 (有可能会调用多次)
 */
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
    // 累加文件大小
    self.currentFileSize += data.length;
    // 计算进度值
    CGFloat progress = (CGFloat)self.currentFileSize / self.expectedContentLength;

    // 拼接数据
    [self writeData:data];
    NSLog(@"progress  =%f", progress);
}

/**
 *  将数据写入文件中
 */
- (void)writeData:(NSData *)data {
    if (self.fp == nil) {

        [data writeToFile:self.destPath atomically:YES];

    } else {
        // 将文件指针移动到文件末尾
        [self.fp seekToEndOfFile];
        // 利用文件指针写入数据,默认是从文件开头拼接数据
        [self.fp writeData:data];
    }
}

/**
 *  请求完毕之后调用
 */
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
    NSLog(@"%s", __FUNCTION__);
    // 文件指针在使用完毕之后要关闭 (必须要有)
    [self.fp closeFile];
}

/**
 *  请求失败/出错时调用 (一定要对错误进行处理)
 */
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
    NSLog(@"%s", __FUNCTION__);
    // 文件指针在使用完毕之后要关闭 (必须要有)
    [self.fp closeFile];
}

#pragma mark - 懒加载数据
- (NSFileHandle *)fp {
    // 创建文件指针对象
    // fileHandleForReadingAtPath:以只读的方式创建文件指针对象
    // fileHandleForWritingAtPath:以写入的方式创建文件指针对象
    // 如果文件不存在,则fp为nil
    if (_fp == nil) {
        //这里只是单纯的获取指定路径的文件的文件指针对象,并没有创建文件对象,因此,在文件还没有创建时试图获取文件指针对象,返回值为nil
        _fp = [NSFileHandle fileHandleForWritingAtPath:self.destPath];
    }
    return _fp;
}
@end

代理方法方式二:

  • 利用NSOutputStream拼接文件
  • 还存在问题就是该代码反复执行多次,文件会不断累加,不断变大.
//ViewController.h

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController

@end
//ViewController.m

#import "ViewController.h"

@interface ViewController () <NSURLConnectionDataDelegate>
/**
 *  要下载文件的总大小
 */
@property (nonatomic, assign) long long expectedContentLength;
/**
 *  当前已经接收文件的大小
 */
@property (nonatomic, assign) long long currentFileSize;
/**
 *  保存下载文件的路径
 */
@property (nonatomic, copy) NSString *destPath;
/**
 *  文件输出流
 */
@property (nonatomic, strong) NSOutputStream *fileStream;
@end

@implementation ViewController

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

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    // 下载文件的URL
    NSString *URLString = @"http://192.168.30.79/117文件操作之字符串与二进制.mp4";
    // 百分号编码
    URLString = [URLString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    // URL
    NSURL *url = [NSURL URLWithString:URLString];

    // Request
    NSURLRequest *request = [NSURLRequest requestWithURL:url];

    // 创建一个URLConnection对象,并立即加载url执行的数据
    [NSURLConnection connectionWithRequest:request delegate:self];
}

#pragma mark - NSURLConnectionDataDelegate 代理方法
/**
 *  接收到服务器响应时调用
 */
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
    // 获得要下载文件的大小
    self.expectedContentLength = response.expectedContentLength;

    // 获得服务器建议保存的文件名
    self.destPath = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:response.suggestedFilename];

    NSLog(@"%@", self.destPath);

    // 创建文件输出流 参数1:文件路径 参数2 YES:已追加形式输出
    self.fileStream = [NSOutputStream outputStreamToFileAtPath:self.destPath append:YES];
    // 打开流 --> 写入数据之前,必须先打开流
    [self.fileStream open];
}
/**
 *  接收到服务器返回的数据就调用 (有可能会调用多次)
 */
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
    // 累加文件大小
    self.currentFileSize += data.length;
    // 计算进度值
    CGFloat progress = (CGFloat)self.currentFileSize / self.expectedContentLength;

    NSLog(@"progress  =%f", progress);
    // 写入数据
    [self.fileStream write:data.bytes maxLength:data.length];
}

/**
 *  请求完毕之后调用
 */
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
    NSLog(@"%s", __FUNCTION__);
    // 关闭流:打开流和关闭必须成对出现
    [self.fileStream close];
}

/**
 *  请求失败/出错时调用 (一定要对错误进行处理)
 */
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
    NSLog(@"%s", __FUNCTION__);
    // 关闭流 :打开流和关闭必须成对出现
    [self.fileStream close];
}

@end

用子线程下载

  • 用子线程下载
  • 实现断点续传
  • 存在问题:下载过程中不断点击,会使下载文件产生混乱,显示的进度产生混乱.
//ViewController.h

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController

@end
//ViewController.m

#import "ViewController.h"

@interface ViewController () <NSURLConnectionDataDelegate>
/**
 *  要下载文件的总大小
 */
@property (nonatomic, assign) long long expectedContentLength;
/**
 *  当前已经接收文件的大小
 */
@property (nonatomic, assign) long long currentFileSize;
/**
 *  保存下载文件的路径
 */
@property (nonatomic, copy) NSString *destPath;
/**
 *  文件输出流
 */
@property (nonatomic, strong) NSOutputStream *fileStream;
/**
 *  连接对象
 */
@property (nonatomic, strong) NSURLConnection *connection;
@end

@implementation ViewController

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

/**
 *  暂停
 *  这是一个按钮的连线
 */
- (IBAction)pause {

    // 一旦调用cancel方法,连接对象就不能再使用,下次请求需要重新创建新的连接对象进行下载.
    [self.connection cancel];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        // 下载文件的URL
        NSString *URLString = @"http://192.168.2.23/12设置数据和frame.mp4";
        // 百分号编码
        URLString = [URLString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
        // URL
        NSURL *url = [NSURL URLWithString:URLString];

        // 检查服务器文件信息
        [self checkServerFileInfo:url];
        // 检查本地文件信息
         self.currentFileSize = [self checkLocalFileInfo];

        // 如果本地文件大小相等服务器文件大小
        if (self.currentFileSize == self.expectedContentLength) {
            NSLog(@"下载完成");
            return;
        }
        // 告诉服务器从指定的位置开始下载文件
        // Request:断点续传缓存策略必须是直接从服务器加载,请求对象必须是可变的。
        NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:15.0];
        // 设置从服务器获取文件的位置(断点续传的位置)
        NSString *range = [NSString stringWithFormat:@"bytes=%lld-",self.currentFileSize];
        [request setValue:range forHTTPHeaderField:@"Range"];
        // 创建一个URLConnection对象,并立即加载url执行的数据(第二次请求,用代理获取文件)
        self.connection = [NSURLConnection connectionWithRequest:request delegate:self];

        // RunLoop:监听事件。网络请求本身是一个事件,需要RunLoop来监听并响应.定时器也是一个事件。
        // 子线程的Runloop默认是不开启,需要手动开启RunLoop
        // 当前网络请求结束之后,系统会默认关闭当前线程的RunLoop.
        // 另一种开启线程的方式:CFRunLoopRun();
        [[NSRunLoop currentRunLoop] run];
        NSLog(@"come here");
    });
}

/**
 *  检查服务器文件信息
 */
- (void)checkServerFileInfo:(NSURL *)url{
    // 使用同步请求获得服务器文件信息(第一次请求,获取服务器文件信息)
    // 请求对象
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    // 设置请求方法,获取下载内容的头信息
    // HEAD请求只用在下载文件之前,获得服务器文件信息。
    request.HTTPMethod = @"HEAD";
    // 响应对象
    NSURLResponse *response = nil;
    // 发送同步请求 --> 两个** 就是传递对象的地址
    [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:NULL];

//    NSLog(@"response = %@",response);
    // 获得要下载文件的大小
    self.expectedContentLength = response.expectedContentLength;

    // 获得服务器建议保存的文件名
    self.destPath = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:response.suggestedFilename];
//    NSLog(@"%@", self.destPath);
}

/**
 *  检查本地文件信息
 */
- (long long)checkLocalFileInfo {
    // 获得文件管理者对象
    NSFileManager *fileMangager = [NSFileManager defaultManager];
    /*
     检查本地文件信息(断点续传逻辑思路)
         > 如果本地没有文件,则从零开始下载
         > 本地文件的大小比服务器文件还大,删除本地文件从零开始下载。
         > 本地文件小于服务器文件,从本地文件大小的位置开始下载。
         > 本地文件等于服务器文件,提示下载完成。
     */
    long long fileSize = 0;
    //判断文件是否存在
    if ([fileMangager fileExistsAtPath:self.destPath]) {
        // 存在,获得本地文件信息
        NSDictionary *fileAttributes = [fileMangager attributesOfItemAtPath:self.destPath error:NULL];
//        NSLog(@"fileAttributes = %@",fileAttributes );
        // 获得文件大小
//        fileAttributes[NSFileSize] longLongValue
        fileSize = [fileAttributes fileSize]; // 10 M
    }

    // 本地文件跟服务器文件进行比较
    if(fileSize > self.expectedContentLength) {
        // 删除本地文件
        [fileMangager removeItemAtPath:self.destPath error:NULL];
        // 从零开始下载
        fileSize = 0;
    }
    return fileSize;
}

#pragma mark - NSURLConnectionDataDelegate 代理方法
/**
 *  接收到服务器响应时调用
 */
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
    // 创建文件输出流 参数1:文件路径 参数2 YES:已追加形式输出
    self.fileStream = [NSOutputStream outputStreamToFileAtPath:self.destPath append:YES];
    // 打开流 --> 写入数据之前,必须先打开流
    [self.fileStream open];
}
/**
 *  接收到服务器返回的数据就调用 (有可能会调用多次)
 */
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
//    NSLog(@"%@", [NSThread currentThread]);
    // 累加文件大小
    self.currentFileSize += data.length;
    // 计算进度值
    CGFloat progress = (CGFloat)self.currentFileSize / self.expectedContentLength;
    [NSThread sleepForTimeInterval:0.1];
    NSLog(@"progress  =%f", progress);
    // 写入数据
    [self.fileStream write:data.bytes maxLength:data.length];
}

/**
 *  请求完毕之后调用
 */
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
    NSLog(@"%s", __FUNCTION__);
    // 关闭流:打开流和关闭必须成对出现
    [self.fileStream close];
}

/**
 *  请求失败/出错时调用 (一定要对错误进行处理)
 */
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
    NSLog(@"%s", __FUNCTION__);
    // 关闭流 :打开流和关闭必须成对出现
    [self.fileStream close];
}

@end

使用单例下载管理器来管理下载

  • 用子线程下载
  • 实现断点续传
  • 解决下载过程中不断点击产生的文件混乱问题和进度混乱问题.
  • 存在问题:没有限制开启的最大线程数,当有大量文件下载时,会按照文件的数量开启相应的线程数.
//ViewController.h

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController

@end
//ViewController.m

#import "ViewController.h"
#import "YFDownloadManager.h"
#import "YFProgressButton.h"

@interface ViewController ()
/**
 *  进度按钮
 *  显示进度的按钮的连线
 */
@property (nonatomic, weak) IBOutlet YFProgressButton *progressBtn;
/**
 *  下载操作
 */
//@property (nonatomic, strong) YFDownloadOperation *downloader;
@property (nonatomic, strong) NSURL *url;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

}

/**
 *  暂停
 *  按钮的连线
 */
- (IBAction)pause {
    // 一旦调用cancel方法,连接对象就不能再使用,下次请求需要重新创建新的连接对象
    [[YFDownloadManager sharedManager] pause:self.url];

}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    // 下载文件的URL
    NSString *URLString = @"http://192.168.2.23/12设置数据和frame.mp4";
    // 百分号编码
    URLString = [URLString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    // URL
    NSURL *url = [NSURL URLWithString:URLString];
    self.url = url;

    // 利用单例接管下载操作
    [[YFDownloadManager sharedManager] downloadWithURL:url progress:^(CGFloat progress) {
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"progress = %f",progress);
            self.progressBtn.progress = progress;
        });
    } finished:^(NSString *destPath, NSError *error) {
         NSLog(@"下载完成 destPath = %@,error = %@",destPath,error);
    }];
}

@end
//单例文件
//YFSingleton.h

// 头文件使用的宏
// ## 表示拼接前后两个字符串
#define YFSingleton_h(name)  + (instancetype)shared##name;

#if __has_feature(objc_arc) // 是arc环境
    #define YFSingleton_m(name)  + (instancetype)shared##name {    return [[self alloc] init];    }        + (instancetype)allocWithZone:(struct _NSZone *)zone {    static dispatch_once_t onceToken;    static id instance = nil;    dispatch_once(&onceToken, ^{     instance = [super allocWithZone:zone];    });    return instance;    }        - (id)copyWithZone:(nullable NSZone *)zone {    return self;    }
#else // MRC环境
    #define YFSingleton_m(name)  + (instancetype)shared##name {    return [[self alloc] init];    }        + (instancetype)allocWithZone:(struct _NSZone *)zone {    static dispatch_once_t onceToken;    static id instance = nil;    dispatch_once(&onceToken, ^{     instance = [super allocWithZone:zone];    });    return instance;    }        - (id)copyWithZone:(nullable NSZone *)zone {    return self;    }    - (oneway void)release {        }        - (instancetype)autorelease {    return self;    }        - (instancetype)retain {    return self;    }        - (NSUInteger)retainCount {    return 1;    }
#endif
//下载操作文件
//YFDownloadOperation.h

#import <UIKit/UIKit.h>

@interface YFDownloadOperation : NSObject
/**
 *  创建下载操作
 *
 *  @param progressBlock 进度回调
 *  @param finishedBlock 完成回调
 *
 *  @return 下载操作
 */
+ (instancetype)downloadOperation:(void (^)(CGFloat progress))progressBlock finishedBlock:(void (^)(NSString *destPath,NSError *error))finishedBlock;

/**
 *  下载URL指定的文件
 */
- (void)download:(NSURL *)URL;
/**
 *  暂停下载
 */
- (void)pause;

/**
 *  根据URL获得文件的下载进度
 */
+ (CGFloat)progressWithURL:(NSURL *)URL;

@end
//YFDownloadOperation.m

#import "YFDownloadOperation.h"

@interface YFDownloadOperation() <NSURLConnectionDataDelegate>
/**
 *  要下载文件的总大小
 */
@property (nonatomic, assign) long long expectedContentLength;
/**
 *  当前已经接收文件的大小
 */
@property (nonatomic, assign) long long currentFileSize;
/**
 *  保存下载文件的路径
 */
@property (nonatomic, copy) NSString *destPath;
/**
 *  文件输出流
 */
@property (nonatomic, strong) NSOutputStream *fileStream;
/**
 *  连接对象
 */
@property (nonatomic, strong) NSURLConnection *connection;
/**
 *  进度回调block
 */
@property (nonatomic, copy) void (^progressBlock)(CGFloat progress);
/**
 *  完成回调
 */
@property (nonatomic, copy) void (^finishedBlock)(NSString *destPath,NSError *error);
@end

@implementation YFDownloadOperation

/**
 *  创建下载操作
 *
 *  @param progressBlock 进度回调
 *  @param finishedBlock 完成回调
 *
 *  @return 下载操作 如果block不是在当前方法执行,需要使用属性引着
 */
+ (instancetype)downloadOperation:(void (^)(CGFloat progress))progressBlock finishedBlock:(void (^)(NSString *destPath,NSError *error))finishedBlock {
    // 使用断言
    NSAssert(finishedBlock != nil, @"必须传人完成回调");
    // 创建下载操作
    YFDownloadOperation *downloader = [[YFDownloadOperation alloc] init];

    // 记录block
    downloader.progressBlock = progressBlock;
    downloader.finishedBlock = finishedBlock;

    return downloader;
}
/**
 *  下载URL指定的文件
 */
- (void)download:(NSURL *)URL {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        // 下载文件的URL
        NSString *URLString = @"http://192.168.2.23/12设置数据和frame.mp4";
        // 百分号编码
        URLString = [URLString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
        // URL
        NSURL *url = [NSURL URLWithString:URLString];

        // 检查服务器文件信息
        [self checkServerFileInfo:url];
        // 检查本地文件信息
        self.currentFileSize = [self checkLocalFileInfo];

        // 如果本地文件大小相等服务器文件大小
        if (self.currentFileSize == self.expectedContentLength) {
            // 完成回调
            dispatch_async(dispatch_get_main_queue(), ^{
                self.finishedBlock(self.destPath,nil);
            });

            // 进度回调
            if(self.progressBlock) {
                self.progressBlock(1);
            }
            return;
        }
        // 告诉服务器从指定的位置开始下载文件
        // Request:断点续传缓存策略必须是直接从服务器加载,请求对象必须是可变的。
        NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:1 timeoutInterval:15.0];
        // 设置Range
        NSString *range = [NSString stringWithFormat:@"bytes=%lld-",self.currentFileSize];
        [request setValue:range forHTTPHeaderField:@"Range"];
        // 创建一个URLConnection对象,并立即加载url执行的数据
        self.connection = [NSURLConnection connectionWithRequest:request delegate:self];
        // RunLoop:监听事件。网络请求本身也是一个事件。定时器本身也是一个。
        // 子线程的Runloop默认是不开启,需要手动开启RunLoop
        //        CFRunLoopRun();
        // 当前网络请求结束之后,系统会默认关闭当前线程的RunLoop.
        [[NSRunLoop currentRunLoop] run];
        NSLog(@"come here");
    });
}

/**
 *  暂停下载
 */
- (void)pause {
    [self.connection cancel];
}

/**
 *  根据URL获得文件的下载进度
 */
+ (CGFloat)progressWithURL:(NSURL *)URL {
    YFDownloadOperation *downloader = [[YFDownloadOperation alloc] init];
    [downloader checkServerFileInfo:URL];
    downloader.currentFileSize = [downloader checkLocalFileInfo];
    return (CGFloat)downloader.currentFileSize / downloader.expectedContentLength;
}
#pragma mark - 私有方法
/**
 *  检查服务器文件信息
 */
- (void)checkServerFileInfo:(NSURL *)url{
    // 使用同步请求获得服务器文件信息
    // 请求对象
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    // 设置请求方法
    // HEAD请求只用在下载文件之前,获取记录文件信息的数据包文头部内容,目的是获得服务器文件信息。
    request.HTTPMethod = @"HEAD";
    // 响应对象,用来保存从服务器返回的内容
    NSURLResponse *response = nil;
    // 发送同步请求 --> 两个** 就是传递对象的地址
    [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:NULL];

    //    NSLog(@"response = %@",response);
    // 获得要下载文件的大小
    self.expectedContentLength = response.expectedContentLength;

    // 获得服务器建议保存的文件名
    self.destPath = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:response.suggestedFilename];
    NSLog(@"%@", self.destPath);
}

/**
 *  检查本地文件信息
 */
- (long long)checkLocalFileInfo {
    // 获得文件管理者对象
    NSFileManager *fileMangager = [NSFileManager defaultManager];
    /*
     检查本地文件信息
     > 如果本地没有文件,则从零开始下载
     > 本地文件的大小比服务器文件还大,删除本地文件从零开始下载。
     > 本地文件小于服务器文件,从本地文件大小的位置开始下载。
     > 本地文件等于服务器文件,提示下载完成。
     */
    long long fileSize = 0;
    if ([fileMangager fileExistsAtPath:self.destPath]) {
        // 存在,获得本地文件信息
        NSDictionary *fileAttributes = [fileMangager attributesOfItemAtPath:self.destPath error:NULL];
        //        NSLog(@"fileAttributes = %@",fileAttributes );
        // 获得文件大小
        //        fileAttributes[NSFileSize] longLongValue
        fileSize = [fileAttributes fileSize]; // 10 M
    }

    // 本地文件跟服务器文件进行比较
    if(fileSize > self.expectedContentLength) {
        // 删除本地文件
        [fileMangager removeItemAtPath:self.destPath error:NULL];
        // 从零开始下载
        fileSize = 0;
    }
    return fileSize;
}
#pragma mark - NSURLConnectionDataDelegate 代理方法
/**
 *  接收到服务器响应时调用
 */
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
    // 创建文件输出流 参数1:文件路径 参数2 YES:已追加形式输出
    self.fileStream = [NSOutputStream outputStreamToFileAtPath:self.destPath append:YES];
    // 打开流 --> 写入数据之前,必须先打开流
    [self.fileStream open];
}
/**
 *  接收到服务器返回的数据就调用 (有可能会调用多次)
 */
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
    //    NSLog(@"%@", [NSThread currentThread]);
    // 累加文件大小
    self.currentFileSize += data.length;
    // 计算进度值
    CGFloat progress = (CGFloat)self.currentFileSize / self.expectedContentLength;
    [NSThread sleepForTimeInterval:0.1];
//    NSLog(@"progress  =%f", progress);
    // 写入数据
    [self.fileStream write:data.bytes maxLength:data.length];

    if (self.progressBlock) {
        self.progressBlock(progress);
    }

}

/**
 *  请求完毕之后调用
 */
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
    NSLog(@"%s", __FUNCTION__);
    // 关闭流:打开流和关闭必须成对出现
    [self.fileStream close];
    // 主线程回调
    dispatch_async(dispatch_get_main_queue(), ^{
        self.finishedBlock(self.destPath,nil);
    });
}

/**
 *  请求失败/出错时调用 (一定要对错误进行错误)
 */
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
    NSLog(@"%s", __FUNCTION__);
    // 关闭流 :打开流和关闭必须成对出现
    [self.fileStream close];
    // 主线程回调
    dispatch_async(dispatch_get_main_queue(), ^{
        self.finishedBlock(nil,error);
    });
}

@end
//单例下载管理器
//YFDownloadManager.h
#import <UIKit/UIKit.h>
#import "YFSingleton.h"
@interface YFDownloadManager : NSObject

YFSingleton_h(Manager)

/**
 *  下载URL指定的文件
 *
 *  @param URL      文件路径
 *  @param progress 进度回调
 *  @param finished 完成回调
 */
- (void)downloadWithURL:(NSURL *)URL progress:(void (^)(CGFloat progress))progress finished:(void (^)(NSString *destPath,NSError *error))finished;

/**
 *  暂停URL指定的下载操作
 */
- (void)pause:(NSURL *)URL;
@end
//YFDownloadManager.m

#import "YFDownloadManager.h"
#import "YFDownloadOperation.h"

@interface YFDownloadManager()
/**
 *  操作缓存池
 */
@property (nonatomic, strong) NSMutableDictionary *operationCache;
@end

@implementation YFDownloadManager
YFSingleton_m(Manager)

/**
 *  下载URL指定的文件
 *
 *  @param URL      文件路径
 *  @param progress 进度回调
 *  @param finished 完成回调
 */
- (void)downloadWithURL:(NSURL *)URL progress:(void (^)(CGFloat progress))progress finished:(void (^)(NSString *destPath,NSError *error))finished {

    if (URL == nil) return;
    // 判断是否存在对应的下载操作
    if(self.operationCache[URL] != nil) {
        NSLog(@"正在拼命下载中...稍安勿躁...");
        return;
    }

    // 利用类方法创建下载操作
    YFDownloadOperation *downloader = [YFDownloadOperation downloadOperation:progress finishedBlock:^(NSString *destPath, NSError *error) {
        // 将操作从缓存池中移除
        [self.operationCache removeObjectForKey:URL];
        // 完成回调
        finished(destPath,error);
    }];
    // 将操作添加到操作缓存池中
    [self.operationCache setObject:downloader forKey:URL];
    // 开始下载
    [downloader download:URL];
}
/**
 *  暂停URL指定的下载操作
 */
- (void)pause:(NSURL *)URL {
    // 根据URL获得下载操作
    YFDownloadOperation *downloader = self.operationCache[URL];
    // 暂停下载
    [downloader pause];
    // 将操作从缓存池中移除
    [self.operationCache removeObjectForKey:URL];
}
#pragma mark - 懒加载数据
- (NSMutableDictionary *)operationCache {
    if (_operationCache == nil) {
        _operationCache = [[NSMutableDictionary alloc] init];
    }
    return _operationCache;
}
@end
//显示进度按钮文件
//YFProgressButton.h

#import <UIKit/UIKit.h>
// IB_DESIGNABLE:表示这个类可以在IB中设置
// IBInspectable:表示这个属性可以在IB中设值
// IB:interface builder 界面构建者

IB_DESIGNABLE
@interface YFProgressButton : UIButton
/**
 *  进度值
 */
@property (nonatomic, assign) IBInspectable CGFloat progress;
/**
 *  线宽
 */
@property (nonatomic, assign) IBInspectable CGFloat lineWidth;
/**
 *  线的颜色
 */
@property (nonatomic, strong) IBInspectable UIColor *lineColor;
@end
//YFProgressButton.m

#import "YFProgressButton.h"

@implementation YFProgressButton

- (void)setProgress:(CGFloat)progress {
    _progress = progress;
    // 设置按钮标题
    [self setTitle:[NSString stringWithFormat:@"%.2f%%",progress * 100] forState:UIControlStateNormal];

    // 通知重绘
    [self setNeedsDisplay];
}

- (void)drawRect:(CGRect)rect {
    // 圆心
    CGPoint center = CGPointMake(rect.size.width * 0.5, rect.size.height * 0.5);
    // 起始角度
    CGFloat startAngle = - M_PI_2;
    // 结束角度
    CGFloat endAngle = 2 * M_PI * self.progress + startAngle;
    // 半价
    CGFloat raduis = (MIN(rect.size.width, rect.size.height) - self.lineWidth) * 0.5;
    UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:center radius:raduis startAngle:startAngle endAngle:endAngle clockwise:YES];
    // 设置线宽
    path.lineWidth = self.lineWidth;
    // 设置颜色
    [self.lineColor set];
    // 渲染
    [path stroke];
}
@end

完美版

//主控制器文件
//ViewController.h

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController

@end
//ViewController.m

#import "ViewController.h"
#import "YFDownloadManager.h"
#import "YFProgressButton.h"

@interface ViewController ()
/**
 *  进度按钮
 */
@property (nonatomic, weak) IBOutlet YFProgressButton *progressBtn;
/**
 *  下载操作
 */
//@property (nonatomic, strong) YFDownloadOperation *downloader;
@property (nonatomic, strong) NSURL *url;
@end

@implementation ViewController

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

/**
 *  暂停
 *  暂停按钮连线
 */
- (IBAction)pause {
//    After this method is called, the connection makes no further delegate method calls. If you want to reattempt the connection, you should create a new connection object.
    // 一旦调用cancel方法,连接对象就不能再使用,下次请求需要重新创建新的连接对象
    [[YFDownloadManager sharedManager] pause:self.url];

}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    // 下载文件的URL
    NSString *URLString = @"http://192.168.2.23/12设置数据和frame.mp4";
    // 百分号编码
    URLString = [URLString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    // URL
    NSURL *url = [NSURL URLWithString:URLString];
    self.url = url;

    // 利用单例接管下载操作
    [[YFDownloadManager sharedManager] downloadWithURL:url progress:^(CGFloat progress) {
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"progress = %f",progress);
            self.progressBtn.progress = progress;
        });
    } finished:^(NSString *destPath, NSError *error) {
         NSLog(@"下载完成 destPath = %@,error = %@",destPath,error);
    }];
}

@end
//单例文件
//YFSingleton.h

// 头文件使用的宏
// ## 表示拼接前后两个字符串
#define YFSingleton_h(name)  + (instancetype)shared##name;

#if __has_feature(objc_arc) // 是arc环境
    #define YFSingleton_m(name)  + (instancetype)shared##name {    return [[self alloc] init];    }        + (instancetype)allocWithZone:(struct _NSZone *)zone {    static dispatch_once_t onceToken;    static id instance = nil;    dispatch_once(&onceToken, ^{     instance = [super allocWithZone:zone];    });    return instance;    }        - (id)copyWithZone:(nullable NSZone *)zone {    return self;    }
#else // MRC环境
    #define YFSingleton_m(name)  + (instancetype)shared##name {    return [[self alloc] init];    }        + (instancetype)allocWithZone:(struct _NSZone *)zone {    static dispatch_once_t onceToken;    static id instance = nil;    dispatch_once(&onceToken, ^{     instance = [super allocWithZone:zone];    });    return instance;    }        - (id)copyWithZone:(nullable NSZone *)zone {    return self;    }    - (oneway void)release {        }        - (instancetype)autorelease {    return self;    }        - (instancetype)retain {    return self;    }        - (NSUInteger)retainCount {    return 1;    }
#endif
//下载操作文件
//YFDownloadOperation.h

#import <UIKit/UIKit.h>

@interface YFDownloadOperation : NSOperation
/**
 *  创建下载操作
 *
 *  @param progressBlock 进度回调
 *  @param finishedBlock 完成回调
 *
 *  @return 下载操作
 */
+ (instancetype)downloadOperationWithURL:(NSURL *)URL progressBlock:(void (^)(CGFloat progress))progressBlock finishedBlock:(void (^)(NSString *destPath,NSError *error))finishedBlock;

/**
 *  暂停下载
 */
- (void)pause;

/**
 *  根据URL获得文件的下载进度
 */
+ (CGFloat)progressWithURL:(NSURL *)URL;

@end
//YFDownloadOperation.m

#import "YFDownloadOperation.h"

@interface YFDownloadOperation() <NSURLConnectionDataDelegate>
/**
 *  要下载文件的总大小
 */
@property (nonatomic, assign) long long expectedContentLength;
/**
 *  当前已经接收文件的大小
 */
@property (nonatomic, assign) long long currentFileSize;
/**
 *  保存下载文件的路径
 */
@property (nonatomic, copy) NSString *destPath;
/**
 *  文件输出流
 */
@property (nonatomic, strong) NSOutputStream *fileStream;
/**
 *  连接对象
 */
@property (nonatomic, strong) NSURLConnection *connection;
/**
 *  进度回调block
 */
@property (nonatomic, copy) void (^progressBlock)(CGFloat progress);
/**
 *  完成回调
 */
@property (nonatomic, copy) void (^finishedBlock)(NSString *destPath,NSError *error);
/**
 *  要下载文件的URL
 */
@property (nonatomic, strong) NSURL *URL;
@end

@implementation YFDownloadOperation

/**
 *  创建下载操作
 *
 *  @param progressBlock 进度回调
 *  @param finishedBlock 完成回调
 *
 *  @return 下载操作 如果block不是在当前方法执行,需要使用属性引着
 */
+ (instancetype)downloadOperationWithURL:(NSURL *)URL progressBlock:(void (^)(CGFloat progress))progressBlock finishedBlock:(void (^)(NSString *destPath,NSError *error))finishedBlock {
    // 使用断言
    NSAssert(finishedBlock != nil, @"必须传人完成回调");
    // 创建下载操作
    YFDownloadOperation *downloader = [[YFDownloadOperation alloc] init];

    // 记录block
    downloader.progressBlock = progressBlock;
    downloader.finishedBlock = finishedBlock;

    // 记录URL
    downloader.URL = URL;

    return downloader;
}

- (void)main {
    @autoreleasepool {
        [self download];
    }
}

/**
 *  下载URL指定的文件
 */
- (void)download{
    //dispatch_async(dispatch_get_global_queue(0, 0), ^{
        // 检查服务器文件信息
        [self checkServerFileInfo:self.URL];
        // 检查本地文件信息
        self.currentFileSize = [self checkLocalFileInfo];

        // 如果本地文件大小相等服务器文件大小
        if (self.currentFileSize == self.expectedContentLength) {
            // 完成回调
            dispatch_async(dispatch_get_main_queue(), ^{
                self.finishedBlock(self.destPath,nil);
            });

            // 进度回调
            if(self.progressBlock) {
                self.progressBlock(1);
            }
            return;
        }
        // 告诉服务器从指定的位置开始下载文件
        // Request:断点续传缓存策略必须是直接从服务器加载,请求对象必须是可变的。
        NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:self.URL cachePolicy:1 timeoutInterval:15.0];
        // 设置Range
        NSString *range = [NSString stringWithFormat:@"bytes=%lld-",self.currentFileSize];
        [request setValue:range forHTTPHeaderField:@"Range"];
        // 创建一个URLConnection对象,并立即加载url执行的数据
        self.connection = [NSURLConnection connectionWithRequest:request delegate:self];
        // RunLoop:监听事件。网络请求本身也是一个事件。定时器本身也是一个。
        // 子线程的Runloop默认是不开启,需要手动开启RunLoop
        //        CFRunLoopRun();
        // 当前网络请求结束之后,系统会默认关闭当前线程的RunLoop.
        [[NSRunLoop currentRunLoop] run];
        NSLog(@"come here");
   // });
}

/**
 *  暂停下载
 */
- (void)pause {
    [self.connection cancel];
}

/**
 *  根据URL获得文件的下载进度
 */
+ (CGFloat)progressWithURL:(NSURL *)URL {
    YFDownloadOperation *downloader = [[YFDownloadOperation alloc] init];
    [downloader checkServerFileInfo:URL];
    downloader.currentFileSize = [downloader checkLocalFileInfo];
    return (CGFloat)downloader.currentFileSize / downloader.expectedContentLength;
}
#pragma mark - 私有方法
/**
 *  检查服务器文件信息
 */
- (void)checkServerFileInfo:(NSURL *)url{
    // 使用同步请求获得服务器文件信息
    // 请求对象
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    // 设置请求方法
    // HEAD请求只用在下载文件之前,获得服务器文件信息。
    request.HTTPMethod = @"HEAD";
    // 响应对象
    NSURLResponse *response = nil;
    // 发送同步请求 --> 两个** 就是传递对象的地址
    [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:NULL];

    //    NSLog(@"response = %@",response);
    // 获得要下载文件的大小
    self.expectedContentLength = response.expectedContentLength;

    // 获得服务器建议保存的文件名
    self.destPath = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:response.suggestedFilename];
    NSLog(@"%@", self.destPath);
}

/**
 *  检查本地文件信息
 */
- (long long)checkLocalFileInfo {
    // 获得文件管理者对象
    NSFileManager *fileMangager = [NSFileManager defaultManager];
    /*
     检查本地文件信息
     > 如果本地没有文件,则从零开始下载
     > 本地文件的大小比服务器文件还大,删除本地文件从零开始下载。
     > 本地文件小于服务器文件,从本地文件大小的位置开始下载。
     > 本地文件等于服务器文件,提示下载完成。
     */
    long long fileSize = 0;
    if ([fileMangager fileExistsAtPath:self.destPath]) {
        // 存在,获得本地文件信息
        NSDictionary *fileAttributes = [fileMangager attributesOfItemAtPath:self.destPath error:NULL];
        //        NSLog(@"fileAttributes = %@",fileAttributes );
        // 获得文件大小
        //        fileAttributes[NSFileSize] longLongValue
        fileSize = [fileAttributes fileSize]; // 10 M
    }

    // 本地文件跟服务器文件进行比较
    if(fileSize > self.expectedContentLength) {
        // 删除本地文件
        [fileMangager removeItemAtPath:self.destPath error:NULL];
        // 从零开始下载
        fileSize = 0;
    }
    return fileSize;
}
#pragma mark - NSURLConnectionDataDelegate 代理方法
/**
 *  接收到服务器响应时调用
 */
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
    // 创建文件输出流 参数1:文件路径 参数2 YES:已追加形式输出
    self.fileStream = [NSOutputStream outputStreamToFileAtPath:self.destPath append:YES];
    // 打开流 --> 写入数据之前,必须先打开流
    [self.fileStream open];
}
/**
 *  接收到服务器返回的数据就调用 (有可能会调用多次)
 */
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
    //    NSLog(@"%@", [NSThread currentThread]);
    // 累加文件大小
    self.currentFileSize += data.length;
    // 计算进度值
    CGFloat progress = (CGFloat)self.currentFileSize / self.expectedContentLength;
    [NSThread sleepForTimeInterval:0.1];
//    NSLog(@"progress  =%f", progress);
    // 写入数据
    [self.fileStream write:data.bytes maxLength:data.length];

    if (self.progressBlock) {
        self.progressBlock(progress);
    }

}

/**
 *  请求完毕之后调用
 */
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
    NSLog(@"%s", __FUNCTION__);
    // 关闭流:打开流和关闭必须成对出现
    [self.fileStream close];
    // 主线程回调
    dispatch_async(dispatch_get_main_queue(), ^{
        self.finishedBlock(self.destPath,nil);
    });
}

/**
 *  请求失败/出错时调用 (一定要对错误进行错误)
 */
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
    NSLog(@"%s", __FUNCTION__);
    // 关闭流 :打开流和关闭必须成对出现
    [self.fileStream close];
    // 主线程回调
    dispatch_async(dispatch_get_main_queue(), ^{
        self.finishedBlock(nil,error);
    });
}

@end
//下载文件管理器
//YFDownloadManager.h

#import <UIKit/UIKit.h>
#import "YFSingleton.h"
@interface YFDownloadManager : NSObject

YFSingleton_h(Manager)

/**
 *  下载URL指定的文件
 *
 *  @param URL      文件路径
 *  @param progress 进度回调
 *  @param finished 完成回调
 */
- (void)downloadWithURL:(NSURL *)URL progress:(void (^)(CGFloat progress))progress finished:(void (^)(NSString *destPath,NSError *error))finished;

/**
 *  暂停URL指定的下载操作
 */
- (void)pause:(NSURL *)URL;
@end
//YFDownloadManager.m

#import "YFDownloadManager.h"
#import "YFDownloadOperation.h"

@interface YFDownloadManager()
/**
 *  操作缓存池
 */
@property (nonatomic, strong) NSMutableDictionary *operationCache;
/**
 *  下载队列
 */
@property (nonatomic, strong) NSOperationQueue *downloadQueue;
@end

@implementation YFDownloadManager
YFSingleton_m(Manager)

/**
 *  下载URL指定的文件
 *
 *  @param URL      文件路径
 *  @param progress 进度回调
 *  @param finished 完成回调
 */
- (void)downloadWithURL:(NSURL *)URL progress:(void (^)(CGFloat progress))progress finished:(void (^)(NSString *destPath,NSError *error))finished {
    if (URL == nil) return;
    // 判断是否存在对应的下载操作
    if(self.operationCache[URL] != nil) {
        NSLog(@"正在拼命下载中...稍安勿躁...");
        return;
    }
    // 利用类方法创建下载操作
    YFDownloadOperation *downloader = [YFDownloadOperation downloadOperationWithURL:URL progressBlock:progress finishedBlock:^(NSString *destPath, NSError *error) {
        // 将操作从缓存池中移除
        [self.operationCache removeObjectForKey:URL];
        // 完成回调
        finished(destPath,error);
    }];
    // 将操作添加到操作缓存池中
    [self.operationCache setObject:downloader forKey:URL];
    // 开始下载
//    [downloader download:URL];
    // 只有NSOperation的子类才可以添加到NSOperationQueue中
    [self.downloadQueue addOperation:downloader];
}
/**
 *  暂停URL指定的下载操作
 */
- (void)pause:(NSURL *)URL {
    // 根据URL获得下载操作
    YFDownloadOperation *downloader = self.operationCache[URL];
    // 暂停下载
    [downloader pause];
    // 将操作从缓存池中移除
    [self.operationCache removeObjectForKey:URL];
}
#pragma mark - 懒加载数据
- (NSMutableDictionary *)operationCache {
    if (_operationCache == nil) {
        _operationCache = [[NSMutableDictionary alloc] init];
    }
    return _operationCache;
}

- (NSOperationQueue *)downloadQueue {
    if (_downloadQueue == nil) {
        _downloadQueue = [[NSOperationQueue alloc] init];
        // 设置最大并发数
        _downloadQueue.maxConcurrentOperationCount = 2;
    }
    return _downloadQueue;
}
@end
//YFProgressButton.h

#import <UIKit/UIKit.h>
// IB_DESIGNABLE:表示这个类可以在IB中设置
// IBInspectable:表示这个属性可以在IB中设值
// IB:interface builder 界面构建者

IB_DESIGNABLE
@interface YFProgressButton : UIButton
/**
 *  进度值
 */
@property (nonatomic, assign) IBInspectable CGFloat progress;
/**
 *  线宽
 */
@property (nonatomic, assign) IBInspectable CGFloat lineWidth;
/**
 *  线的颜色
 */
@property (nonatomic, strong) IBInspectable UIColor *lineColor;
@end
//YFProgressButton.m

#import "YFProgressButton.h"

@implementation YFProgressButton

- (void)setProgress:(CGFloat)progress {
    _progress = progress;
    // 设置按钮标题
    [self setTitle:[NSString stringWithFormat:@"%.2f%%",progress * 100] forState:UIControlStateNormal];

    // 通知重绘
    [self setNeedsDisplay];
}

- (void)drawRect:(CGRect)rect {
    // 圆心
    CGPoint center = CGPointMake(rect.size.width * 0.5, rect.size.height * 0.5);
    // 起始角度
    CGFloat startAngle = - M_PI_2;
    // 结束角度
    CGFloat endAngle = 2 * M_PI * self.progress + startAngle;
    // 半价
    CGFloat raduis = (MIN(rect.size.width, rect.size.height) - self.lineWidth) * 0.5;
    UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:center radius:raduis startAngle:startAngle endAngle:endAngle clockwise:YES];
    // 设置线宽
    path.lineWidth = self.lineWidth;
    // 设置颜色
    [self.lineColor set];
    // 渲染
    [path stroke];
}
@end
时间: 2024-10-29 03:52:02

下载文件思路的相关文章

ios开发网络学习五:MiMEType ,多线程下载文件思路,文件的压缩和解压缩

一:MiMEType:一般可以再百度上搜索到相应文件的MiMEType,或是利用c语言的api去获取文件的MiMEType : //对该文件发送一个异步请求,拿到文件的MIMEType - (void)MIMEType { //    NSString *file = @"file:///Users/文顶顶/Desktop/test.png"; [NSURLConnection sendAsynchronousRequest:[NSURLRequest requestWithURL:[

使用多线程下载文件思路

01 开启多条线程,每条线程都只下载文件的一部分(通过设置请求头中的Range来实现) 02 创建一个和需要下载文件大小一致的文件,判断当前是那个线程,根据当前的线程来判断下载的数据应该写入到文件中的哪个位置.(假设开5条线程来下载10M的文件,那么线程1下载0-2M,线程2下载2-4M一次类推,当接收到服务器返回的数据之后应该先判断当前线程是哪个线程,假如当前线程是线程2,那么在写数据的时候就从文件的2M位置开始写入) 03 代码相关:使用NSFileHandle这个类的seekToFileO

HDFS设计思路,HDFS使用,查看集群状态,HDFS,HDFS上传文件,HDFS下载文件,yarn web管理界面信息查看,运行一个mapreduce程序,mapreduce的demo

26 集群使用初步 HDFS的设计思路 l 设计思想 分而治之:将大文件.大批量文件,分布式存放在大量服务器上,以便于采取分而治之的方式对海量数据进行运算分析: l 在大数据系统中作用: 为各类分布式运算框架(如:mapreduce,spark,tez,--)提供数据存储服务 l 重点概念:文件切块,副本存放,元数据 26.1 HDFS使用 1.查看集群状态 命令:   hdfs  dfsadmin –report 可以看出,集群共有3个datanode可用 也可打开web控制台查看HDFS集群

qt http 下载文件

本文章介绍如何利用HTTP从网站上下载文件.在Qt网络编程中,需要用到协议,即HTTP.它是超文本传输协议,它是一种文件传输协议.对于HTTP就不多解释了. 在Qt网络编程中,需要用到协议,即HTTP.它是超文本传输协议,它是一种文件传输协议.这一节中我们将讲解如何利用HTTP从网站上下载文件.使用的编程环境为Windows下基于Qt4.6.3的Qt Creator 1.3.1 一.最简单的实现. 1.我们新建Qt 4 Gui QApplication . 工程名为“http”,然后选中QtNe

php批量下载文件

最近用codeigniter开发一个图片网站,发现单文件下载很容易实现,批量下载的话,不是很容易实现. 以下是参考网上的例子,编写的一个测试文件,遇到同样问题而不知道如何处理的朋友们可以参考下. 思路: ① 把要下载的图片信息组合到$imgs数组中 ② 把要下载的图片打包成zip压缩包 ③ 下载压缩包,删除服务器上的临时zip文件 <?php $imgs[] = 'http://demo.pic.com/column_f/large/IMG_Af8P_15.jpg'; $imgs[] = 'ht

Android中AsyncTask进行后台下载文件并在下拉菜单显示下载进度

在开发过程中,总会需要从网络上下载文件,有时候还需要将下载进度显示在下拉菜单中. 现在写了一个Demo,封装了AsyncTask下载文件和进度显示的代码,以后在做项目的时候能够直接进行使用. 效果图: 主界面只有一个按钮,比较简单: / layout / activity_main.xml : <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="h

Java FTP下载文件以及编码问题小结

问题 之前在开发过程中,遇到了一点问题,我要访问一个FTP服务器去下载文件详细情况如下: 1. 需要传入一个可能为中文的文件名: 2. 通过文件名去FTP上寻找该文件: 3. FTP服务器的命名编码为“GBK”: 思路 1.通过GET方法直接将文件名负载URL后面,但需要通过转码: 2.在Java Controller中收到参数后,进行解码,解码为正常数据: 3.用正常数据再转码为GBK,到Service中去调用FTP即可 4.(因公司安全考虑,我们需要在另一个模块中调用FTP)通过rest接口

JWebFileTrans(JDownload): 一款可以从网络上下载文件的小程序(二)

一  前言 本文是上一篇博客JWebFileTrans:一款可以从网络上下载文件的小程序(一)的续集.此篇博客主要在上一篇的基础上加入了断点续传的功能,用户在下载中途停止下载后,下次可以读取断点文件,接着上次已经下载的部分文件继续下载.另外将程序名从JWebFileTrans更改为JDownload,并从github的utility repository中独立出来专门维护,后续会添加多线程.ftp下载等功能.JDownload的github链接请点击JDownload源代码 . 另外时隔三个月后

js 下载文件流

这个问题,先描述下最初的思路: 1.从接口中获取到文件流: 2.在浏览器中下载文件流: 按照上述问题在网上找答案,DOM File API,XMLHTTP,asp方法好多. 最后用最简单的方法window.location.href = apiUrl;直接实现了. 现在想想,就是一步下载,分开走就麻烦了.