下载文件
直接请求获取:
- 这种方式会将数据全部接收回来,然后一次性存储到文件中,会出现内存峰值问题,也没有进度跟进
//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