[NSData dataWithContentsOfURL:] 就是一種文件下載方式,Get請求objective-c
可是這種下載方式須要放到子線程中服務器
NSURL *url = [NSURL URLWithString:@"http://10.167.20.151:8080/Admin/help/test.png"]; NSData *data = [NSData dataWithContentsOfURL:url];
經過NSURLConnection發送一個異步的Get請求,一次性將整個文件返回網絡
NSURL* url = [NSURL URLWithString:@"http://10.167.20.151:8080/Admin/help/test.png"]; [NSURLConnection sendAsynchronousRequest:[NSURLRequest requestWithURL:url] queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) { //atomically:原子性 [data writeToFile:filePath atomically:YES]; }];
大文件下載不能一次性返回整個文件,不然會形成內存泄漏,系統崩潰session
// 發送請求去下載 (建立完conn對象後,會自動發起一個異步請求)多線程
IOS9已經廢棄:[NSURLConnection connectionWithRequest:request delegate:self];app
須要實現NSURLConnectionDataDelegate 協議,經常使用的幾個以下:異步
/** * 請求失敗時調用(請求超時、網絡異常) */ - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error; /** * 1.接收到服務器的響應就會調用 */ - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response; /** * 2.當接收到服務器返回的實體數據時調用(具體內容,這個方法可能會被調用屢次) */ - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data; /** * 3.加載完畢後調用(服務器的數據已經徹底返回後) */ - (void)connectionDidFinishLoading:(NSURLConnection *)connection;
didReceiveData方法會被頻繁的調用,每次都會傳回來一部分data性能
最終咱們把每次傳回來的數據合併成一個咱們須要的文件。atom
一般合併文件是定義一個全局的NSMutableData,經過[mutableData appendData:data];來合併,最後將Data寫入沙盒url
代碼以下:
@property (weak, nonatomic) IBOutlet UIProgressView *progressView; @property (nonatomic, strong) NSURLRequest *request; @property (nonatomic, strong) NSURLResponse *response; @property (nonatomic, strong) NSMutableData *fileData; @property (nonatomic, assign) long long fileLength; //>>文件長度 @property (nonatomic, strong) NSString *fileName; //>>文件名
- (void)viewDidLoad { [super viewDidLoad]; _request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://10.167.20.151:8080/Admin/help/objective-c.pdf"]]; //IOS9.0已經被廢棄 [NSURLConnection connectionWithRequest:_request delegate:self]; } /** *接收到服務器的響應 */ - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{ self.response = response; self.fileData = [NSMutableData data]; //獲取下載文件大小 self.fileLength = response.expectedContentLength; //獲取文件名 self.fileName = response.suggestedFilename; } /** *接收到服務器返回的數據(可能被調用屢次) */ - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{ [self.fileData appendData:data]; //更新畫面中的進度 double progress = (double)self.fileData.length/self.fileLength; self.progressView.progress = progress; } /** *服務器返回數據完了 */ - (void)connectionDidFinishLoading:(NSURLConnection *)connection{ NSString *cache = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject]; NSString *filePath = [cache stringByAppendingPathComponent:_fileName]; [self.fileData writeToFile:filePath atomically:YES]; }
可是有個致命的問題,內存!用來接受文件的NSMutableData一直都在內存中,會隨着文件的下載一直變大,
合理的方式在咱們獲取一部分data的時候就寫入沙盒中,而後釋放內存中的data
要用到NSFilehandle這個類,這個類能夠實現對文件的讀取、寫入、更新
每次接收到數據的時候就拼接文件後面,經過- (unsigned long long)seekToEndOfFile;方法
/** *接收到服務器的響應 */ - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response { // 文件路徑 NSString* caches = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject]; NSString* filepath = [ceches stringByAppendingPathComponent:response.suggestedFilename]; // 建立一個空的文件到沙盒中 NSFileManager* fileManager = [NSFileManager defaultManager]; [fileManager createFileAtPath:filepath contents:nil attributes:nil]; // 建立一個用來寫數據的文件句柄對象:用來填充數據 self.writeHandle = [NSFileHandle fileHandleForWritingAtPath:filepath]; // 得到文件的總大小 self.fileLength = response.expectedContentLength; } /** * 2.當接收到服務器返回的實體數據時調用(具體內容,這個方法可能會被調用屢次) */ - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data { // 移動到文件的最後面 [self.writeHandle seekToEndOfFile]; // 將數據寫入沙盒 [self.writeHandle writeData:data]; // 累計寫入文件的長度 self.currentLength += data.length; // 下載進度 self.pregressView.progress = (double)self.currentLength / self.fileLength; } /** * 3.加載完畢後調用(服務器的數據已經徹底返回後) */ - (void)connectionDidFinishLoading:(NSURLConnection *)connection { self.currentLength = 0; self.fileLength = 0; // 關閉文件 [self.writeHandle closeFile]; self.writeHandle = nil; }
下載過程當中內存就會一直很穩定了,而且下載的文件也是沒問題的
斷點續傳的response狀態碼爲206
暫停/繼續下載也是如今下載中必備的功能
NSURLConnection 只提供了一個cancel方法,這並非暫停,而是取消下載任務。若是要實現斷點下載必需要了解HTTP協議中請求頭的Range
經過設置請求頭的Range咱們能夠指定下載的位置、大小
Range實例:
bytes = 0-499 從0到499的頭500個字節 bytes = 500-999 從500到999的第二個500個字節 bytes = 500- 500以後的全部字節 bytes = -500 最後500個字節 bytes = 0-599,700-899 同時指定多個範圍
||
pragma mark --按鈕點擊事件, - (IBAction)btnClicked:(UIButton *)sender { // 狀態取反 sender.selected = !sender.isSelected; // 斷點下載 if (sender.selected) { // 繼續(開始)下載 // 1.URL NSURL *url = [NSURL URLWithString:@"http://10.167.20.151:8080/Admin/help/Code.pdf"]; // 2.請求 NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; // 設置請求頭 NSString *range = [NSString stringWithFormat:@"bytes=%lld-", self.currentLength]; [request setValue:range forHTTPHeaderField:@"Range"]; // 3.下載 self.connection = [NSURLConnection connectionWithRequest:request delegate:self]; } else { // 暫停 [self.connection cancel]; self.connection = nil; } }
@interface NSURLConnectionViewController ()<NSURLConnectionDataDelegate> @property (weak, nonatomic) IBOutlet UIProgressView *progressView; @property (nonatomic, strong) NSURL *url; @property (nonatomic, strong) NSURLRequest *request; @property (nonatomic, strong) NSHTTPURLResponse *response; @property (nonatomic, strong) NSURLConnection *connection; @property (nonatomic, strong) NSFileHandle *fileHandle; @property (nonatomic, assign) long long currentLength; //>>寫入文件的長度 @property (nonatomic, assign) long long fileLength; //>>文件長度 @property (nonatomic, strong) NSString *fileName; //>>文件名 @end @implementation NSURLConnectionViewController - (void)viewDidLoad { [super viewDidLoad]; NSLog(@"%@", NSHomeDirectory()); self.url = [NSURL URLWithString:@"http://10.167.20.151:8080/AdminConsole/help/objective-c.pdf"]; } #pragma mark - NSURLConnectionDataDelegate /** *請求失敗 */ -(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{ NSLog(@"error"); } /** *接收到服務器的響應 */ - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{ self.response = (NSHTTPURLResponse *)response; if (self.response.statusCode == 206) {//!!!斷點續傳的狀態碼爲206 if (self.currentLength) { return; } //獲取下載文件大小 self.fileLength = response.expectedContentLength; //獲取文件名 self.fileName = response.suggestedFilename; //文件路徑 NSString *caches = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject]; NSString *filePath = [caches stringByAppendingPathComponent:_fileName]; //建立一個空的文件到沙盒 NSFileManager *fileManager = [NSFileManager defaultManager]; [fileManager createFileAtPath:filePath contents:nil attributes:nil]; //建立一個用來寫數據的文件句柄 self.fileHandle = [NSFileHandle fileHandleForWritingAtPath:filePath]; }else{ [self.connection cancel]; self.connection = nil; NSLog(@"該文件不存在"); } } /** *接收到服務器返回的數據(可能被調用屢次) */ - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{ //移動到文件末尾 [self.fileHandle seekToEndOfFile]; //寫入數據到文件 [self.fileHandle writeData:data]; self.currentLength += data.length; //更新畫面中的進度 double progress = (double)self.currentLength/self.fileLength; self.progressView.progress = progress; } /** *服務器返回數據完了 */ - (void)connectionDidFinishLoading:(NSURLConnection *)connection{ self.currentLength = 0; self.fileLength = 0; [self.fileHandle closeFile]; self.fileHandle = nil; } - (IBAction)pauseDownLoad:(UIButton *)sender { //暫停<->開始轉換 sender.selected = !sender.isSelected; if (sender.selected) {//開始下載 NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:_url]; //設置請求頭(GET) NSString *range = [NSString stringWithFormat:@"bytes=%lld-", self.currentLength]; [request setValue:range forHTTPHeaderField:@"Range"]; self.connection = [NSURLConnection connectionWithRequest:request delegate:self]; }else{ //暫停 [self.connection cancel]; self.connection = nil; } } @end
在下載過程當中,爲了提升效率,充分利用cpu性能,一般會執行多線程下載。。。待更新!!!
生命週期的兩種方式-1:系統代理-Block方式(流程簡單-優先)
-2:指定代理類:delegate(流程複雜)
上面這種下載文件的方式確實比較複雜,要本身去控制內存寫入相應的位置
iOS7推出了一個新的類NSURLSession,它具有了NSURLConnection所具有的方法,同時也比它更強大
NSURLSession 也能夠發送Get/Post請求,實現文件的下載和上傳。
在NSURLSesiion中,任何請求均可以被看作是一個任務。其中有三種任務類型
NSURLSessionDataTask : 普通的GET\POST請求 NSURLSessionDownloadTask : 文件下載 NSURLSessionUploadTask : 文件上傳(不多用,通常服務器不支持)
NSURLSession發送請求很是簡單,與connection不一樣的是,任務建立後不會自動發送請求,須要手動開始執行任務
// 1.獲得session對象 NSURLSession* session = [NSURLSession sharedSession]; NSURL* url = [NSURL URLWithString:@""]; // 2.建立一個task,任務(GET) NSURLSessionDataTask* dataTask = [session dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { // data 爲返回數據 }]; // 3.開始任務 [dataTask resume];
POST請求:能夠自定義請求頭
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request completionHandler:(void (^)(NSData *data, NSURLResponse *response, NSError *error))completionHandler;
使用NSURLSession就很是簡單了,不須要去考慮什麼邊下載邊寫入沙盒的問題,蘋果都幫咱們作好了
只用將下載好的文件經過NSFileManager剪切到指定位置
須要用到NSURLSession的子類:NSURLSessionDownloadTask
NSURL* url = [NSURL URLWithString:@"http://10.167.20.151:8080/Admin/help/test.png"]; // 獲得session對象 NSURLSession* session = [NSURLSession sharedSession]; // 建立任務 NSURLSessionDownloadTask* downloadTask = [session downloadTaskWithURL:url completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) { //將文件遷移到指定路徑下 NSString *caches = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject]; NSString *file = [caches stringByAppendingPathComponent:response.suggestedFilename]; // 將臨時文件剪切或者複製Caches文件夾 NSFileManager *fileManager = [NSFileManager defaultManager]; // AtPath : 剪切前的文件路徑 // ToPath : 剪切後的文件路徑 [fileManager moveItemAtPath:location.path toPath:file error:nil]; }]; // 開始任務 [downloadTask resume];
/** * 下載完畢會調用 */ - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location {...} /** * 每次寫入沙盒完畢調用 * 在這裏面監聽下載進度,totalBytesWritten/totalBytesExpectedToWrite * * @param bytesWritten 此次寫入的大小 * @param totalBytesWritten 已經寫入沙盒的大小 * @param totalBytesExpectedToWrite 文件總大小 */ - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite {...} /** * 恢復下載後調用 */ - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes {...}
:fa-font:1.暫停下載
resumeData,該參數包含了繼續下載文件的位置信息
resumeData只包含了url跟已經下載了多少數據,不會很大,不用擔憂內存問題
- (void)cancelByProducingResumeData:(void (^)(NSData *resumeData))completionHandler;
!!!須要注意的是Block中循環引用的問題
__weak typeof(self) selfVc = self; [self.downloadTask cancelByProducingResumeData:^(NSData *resumeData) { selfVc.resumeData = resumeData; selfVc.downloadTask = nil; }];
:fa-bold:2.恢復下載
- (NSURLSessionDownloadTask *)downloadTaskWithResumeData:(NSData *)resumeData;
@interface NSURLSessionController () <NSURLSessionDownloadDelegate> @property (weak, nonatomic) IBOutlet UIProgressView *myPregress; //下載任務 @property (nonatomic, strong) NSURLSessionDownloadTask* downloadTask; //resumeData記錄下載位置 @property (nonatomic, strong) NSData* resumeData; @property (nonatomic, strong) NSURLSession* session; @end @implementation NSURLSessionController /** * session的懶加載 */ - (NSURLSession *)session { if (nil == _session) { NSURLSessionConfiguration *cfg = [NSURLSessionConfiguration defaultSessionConfiguration]; self.session = [NSURLSession sessionWithConfiguration:cfg delegate:self delegateQueue:[NSOperationQueue mainQueue]]; } return _session; } - (void)viewDidLoad { [super viewDidLoad]; } /** * 從0開始下載 */ - (void)startDownload { NSURL* url = [NSURL URLWithString:@"http://10.167.20.151:8080/Admin/help/objective-c.pdf"]; // 建立任務 self.downloadTask = [self.session downloadTaskWithURL:url]; // 開始任務 [self.downloadTask resume]; } /** * 恢復下載 */ - (void)resume { // 傳入上次暫停下載返回的數據,就能夠恢復下載 self.downloadTask = [self.session downloadTaskWithResumeData:self.resumeData]; [self.downloadTask resume]; // 開始任務 self.resumeData = nil; } /** * 暫停 */ - (void)pause { __weak typeof(self) selfVc = self; [self.downloadTask cancelByProducingResumeData:^(NSData *resumeData) { // resumeData : 包含了繼續下載的開始位置\下載的url selfVc.resumeData = resumeData; selfVc.downloadTask = nil; }]; } #pragma mark -- NSURLSessionDownloadDelegate /** * 下載完畢會調用 * * @param location 文件臨時地址 */ - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location { //文件路徑 NSString *caches = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject]; NSString *file = [caches stringByAppendingPathComponent:downloadTask.response.suggestedFilename]; // 將臨時文件剪切或者複製Caches文件夾 NSFileManager *fileManager = [NSFileManager defaultManager]; // AtPath : 剪切前的文件路徑 // ToPath : 剪切後的文件路徑 [fileManager moveItemAtPath:location.path toPath:file error:nil]; NSLog(@"下載完成"); } /** * 每次寫入沙盒完畢調用 * 在這裏面監聽下載進度,totalBytesWritten/totalBytesExpectedToWrite * * @param bytesWritten 此次寫入的大小 * @param totalBytesWritten 已經寫入沙盒的大小 * @param totalBytesExpectedToWrite 文件總大小 */ - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite { self.myPregress.progress = (double)totalBytesWritten/totalBytesExpectedToWrite; } /** * 恢復下載後調用, */ - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes { } #pragma mark --按鈕點擊事件 - (IBAction)btnClicked:(UIButton *)sender { // 按鈕狀態取反 sender.selected = !sender.isSelected; if (nil == self.downloadTask) { // 開始(繼續)下載 if (self.resumeData) { // 繼續下載 [self resume]; }else{ // 從0開始下載 [self startDownload]; } }else{ // 暫停 [self pause]; } }