下載文件
直接請求獲取:
- 這種方式會將數據全部接收回來,然後一次性存儲到文件中,會出現內存峯值問題,也沒有進度跟進
//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