我的前面兩篇文章介紹了NSURLSession套件的使用和NSURLSession套件的主要類。今天我們使用NSURLSession來完成一個小的應用程序。在實戰之前,我先補充一點,為什么蘋果會主推NSURLSession技術,而放棄大家都熟悉的NSURLConnection技術,蘋果這么做肯定是有原因的,下面列舉了NSURLSession的優點:
1.后台上傳和下載。當你的程序退出了也能進行網絡操作,這對用戶和APP來說都是個好消息,不用運行APP就可以下載和上傳,這樣更節約手機電量。
2.能夠暫停和恢復網絡操作。不需要使用NSOperation就可以實現暫停、繼續、重啟等操作。
3.可配置的容器。
4.可以子類化並且可以設置私有存儲方式。可以修改數據的存儲方式和存儲位置。
5.改進了授權處理機制。
6.代理更強大。
7.通過文件系統上傳和下載。
好了,進入整體開始我們的實戰,開發一個小的APP叫《ByteClub》。這篇文章的實戰我是參考國外的網站做的,原文《NSURLSession Tutorial》,地址:http://www.raywenderlich.com/51127/nsurlsession-tutorial。覺得它有點啰嗦,英文好的也可以看原文。
我沒有去原文翻譯它,參考它做完例子之后,我按照自己的思路寫的本教程。
准備工作
1.如果你打算跟我一起動手做的話,您需要一個翻牆工具,因為我需要使用dropbox(類似百度雲盤)做http網絡服務器,它在國內被牆掉了😢,我使用的是lantern.下載地址:http://pan.baidu.com/s/1hqhQqHI。下載完記得一定要安裝和運行起來。
2.在dropbox網站注冊成為開發者,然后創建一個APP。Dropbox開發者應用注冊地址:https://www.dropbox.com/developers/apps。
3.然后下載dropbox for mac,我自己准備好了安裝文件DropboxInstaller.dmg,下載地址:http://pan.baidu.com/s/1sjDvZNB。然后安裝,安裝好后在跟目錄下創建byteclub的目錄。如圖:
4.請在byteclub目錄中,隨便創建或者復制進來幾個文件,然后會同步到服務器上,然后我們的第一步開發工作,就是從服務器讀取到這些文件的文件名。
5.然后再下載起始項目,代碼把UI界面以及dropbox的http接口做好了封裝,以便我們專注於NSURLSession部分的實戰和學習。起始項目代碼下載地址:http://pan.baidu.com/s/1mg8M2Vm。
6.如果您想查看最終效果,可以下載我實戰完成后的代碼。下載地址:http://pan.baidu.com/s/1sj48BAP
項目完成后的效果圖:
您可以先稍微熟悉下初始項目。
開工干活
第一階段:讀取Dropbox的跟目錄文件名,並顯示。
1.打開Dropbox.m將您的apiKey和appSecret,appFolder設置進去,前兩者認證需呀,后者是我們在准備階段在Dropbox創建的目錄,比如我的設置為:
static NSString *apiKey = @"rctz909lpd47vyq"; static NSString *appSecret = @“odz1qfezg4ij3pz"; NSString * const appFolder = @“byteclub";
然后你可以運行看看,按照引導信息,輸入您在dropbox的賬號信息,然后應該就可以認證通過了。
2.在NotesViewController.m文件中添加一個會話屬性,用於保存我們的會話。
/** * 會話 */ @property (nonatomic, strong) NSURLSession *session;
3.在initWithStyle:前面添加另外一個初始化方法:
- (instancetype)initWithCoder:(NSCoder *)aDecoder { self = [super initWithCoder:aDecoder]; //下面內容為創建會話 if (self) { //會話配置,這里配置為短暫配置,還有默認配置和后台配置 NSURLSessionConfiguration *config = [NSURLSessionConfiguration ephemeralSessionConfiguration]; //配置請求頭 [config setHTTPAdditionalHeaders:@{@"Authorization":[Dropbox apiAuthorizationHeader]}]; //初始化會話 _session = [NSURLSession sessionWithConfiguration:config]; } return self; }
4.找到notesOnDropbox:方法,然方法內輸入如下代碼:
//顯示加載提示 [UIApplication sharedApplication].networkActivityIndicatorVisible = YES; //獲取你的Dropbox的根目錄 NSURL *url = [Dropbox appRootURL]; //創建數據任務,這個方法主要用來請求HTTP的GET方法,並返回NSData對象,我們需要將數據再解析成我們需要的數據 NSURLSessionDataTask *dataTask = [self.session dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { if (!error) { // 響應狀態代碼為200,代表請求數據成功,判斷成功后我們再進行數據解析 NSHTTPURLResponse *httpResp = (NSHTTPURLResponse*) response; if (httpResp.statusCode == 200) { NSError *jsonError; //解析NSData數據 NSDictionary *notesJSON = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:&jsonError]; NSMutableArray *notesFound = [[NSMutableArray alloc] init]; if (!jsonError) { // 獲取contents鍵值,文件路徑保存在這里 NSArray *contentsOfRootDirectory = notesJSON[@"contents"]; for (NSDictionary *data in contentsOfRootDirectory) { if (![data[@"is_dir"] boolValue]) { DBFile *note = [[DBFile alloc] initWithJSONData:data]; [notesFound addObject:note]; } } //排序 [notesFound sortUsingComparator: ^NSComparisonResult(id obj1, id obj2) { return [obj1 compare:obj2]; }]; self.notes = notesFound; // NSURLSession的方法是在異步執行的,所以更新UI回到主線程 dispatch_async(dispatch_get_main_queue(), ^{ [UIApplication sharedApplication].networkActivityIndicatorVisible = NO; [self.tableView reloadData]; }); } } } }]; //啟動任務 [dataTask resume];
注釋已經比較清晰,如果我們查看Dropbox的文檔就會發現,私用這個URL返回的數據就是json數據,數據格式如下:
{ "hash": "6a29b68d106bda4473ffdaf2e94c4b61", "revision": 73052, "rev": "11d5c00e1cf6c", "thumb_exists": false, "bytes": 0, "modified": "Sat, 10 Aug 2013 21:56:50 +0000", "path": "/byteclub", "is_dir": true, "icon": "folder", "root": "dropbox", "contents": [{ "revision": 73054, "rev": "11d5e00e1cf6c", "thumb_exists": false, "bytes": 16, "modified": "Sat, 10 Aug 2013 23:21:03 +0000", "client_mtime": "Sat, 10 Aug 2013 23:21:02 +0000", "path": "/byteclub/test.txt", "is_dir": false, "icon": "page_white_text", "root": "dropbox", "mime_type": "text/plain", "size": "16 bytes" }], "size": "0 bytes" }
dropbox服務器的返回狀態碼有如下一些:
400 –代表參數有誤.
401 – token錯誤或過期.
403 – 錯誤 OAuth 請求
404 – 請求的文件和目錄不存在
405 – 請求方法錯誤,一般我們只使用get 和post
429 –程序請求次數過多
503 –請再次嘗試
507 –使用Dropbox空間超過配額限制
5xx – 服務器錯誤
5.運行一下看看效果,我的效果是這樣的:
第一階段的實戰完成,你可以先復習一下內容,然后再進行第二階段的實戰。
第二階段的實戰
通過APP輸入內容,並通過文件的形式保存到Dropbox服務器並然后顯示。
1.點擊右上角的“+”按鈕,跳轉到添加Notes界面,你可以輸入內容,但目前沒什么作用,我們需要實現這些功能。打開NotesViewController.m文件,找到prepareForSegue:sender:跳轉方法,在代碼showNote.delegate = self后添加下面一行代碼,將會話session傳遞到新的頁面,這樣我們就可以在新的頁面使用同一個會話了:
//傳遞會話 showNote.session = _session;
2.找到新頁面所屬類的實現文件NoteDetailsViewController.m文件,找到 (IBAction)done:(id)sender方法,這是我們點擊done按鈕需要執行的方法,替換為如下代碼:
- (IBAction)done:(id)sender { // must contain text in textview if (![_textView.text isEqualToString:@""]) { // check to see if we are adding a new note if (!self.note) { DBFile *newNote = [[DBFile alloc] init]; newNote.root = @"dropbox"; self.note = newNote; } _note.contents = _textView.text; _note.path = _filename.text; // - 上傳文件到 DROPBOX - // // 獲取需要上傳文件的路徑 NSURL *url = [Dropbox uploadURLForPath:_note.path]; // 創建請求,這里使用了put方法 NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url]; [request setHTTPMethod:@"PUT"]; //數據 NSData *noteContents = [_note.contents dataUsingEncoding:NSUTF8StringEncoding]; // 上傳任務,NSURLSessionUploadTask支持文件,NSData,數據流stream的類型數據上傳 NSURLSessionUploadTask *uploadTask = [_session uploadTaskWithRequest:request fromData:noteContents completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { //根據HTTP返回的代號確定是否成功,200代表成功,成功后我調用代理方法 NSHTTPURLResponse *httpResp = (NSHTTPURLResponse*) response; if (!error && httpResp.statusCode == 200) { [self.delegate noteDetailsViewControllerDoneWithDetails:self]; } else { // alert for error saving / updating note } }]; // 必須要的動作,啟動任務 [uploadTask resume]; } else { UIAlertView *noTextAlert = [[UIAlertView alloc] initWithTitle:@"輸入為空" message:@"總得輸入點啥吧,親" delegate:nil cancelButtonTitle:@"好" otherButtonTitles:nil]; [noTextAlert show]; } }
3.打開NoteDetailsViewController.m方法,找到方法retreiveNoteText:,替換為如下內容:
-(void)retreiveNoteText { // 根據Dropbox API設置要查看的note的請求路徑 NSString *fileApi = @"https://api-content.dropbox.com/1/files/dropbox"; NSString *escapedPath = [_note.path stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding]; NSString *urlStr = [NSString stringWithFormat: @"%@/%@", fileApi,escapedPath]; NSURL *url = [NSURL URLWithString: urlStr]; [UIApplication sharedApplication].networkActivityIndicatorVisible = YES; // 執行下載數據任務 [[_session dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { if (!error) { NSHTTPURLResponse *httpResp = (NSHTTPURLResponse*) response; if (httpResp.statusCode == 200) { // 數據轉字符串 NSString *text = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding]; dispatch_async(dispatch_get_main_queue(), ^{ [UIApplication sharedApplication].networkActivityIndicatorVisible = NO; self.textView.text = text; }); } else { // 處理錯誤的響應 // } } else { // 處理意外錯誤 // } // 一定要創建任務后啟動哦 }] resume]; }
4.運行看看,比如輸入內容,點擊done按鈕,過一會兒你查看下你的byteclub目錄,應該會創建一個新的文件。我的運行效果是這樣的:
好了,我們的第二階段任務就完成,還是建議您復習一下剛才的內容,然后我們開始第三階段的實戰。
第三階段
使用NSURLSessionTask的代理方法發送圖片到dropbox
1.請在byteclub目錄下新建一個photos目錄,然后拖一些你的圖片到里面來,等待一會兒,圖片應該就會上傳完。
2.打開PhotosViewController.m,找到tableView:cellForRowAtIndexPath:方法,替換為下面內容:

3.相同文件,找到refreshPhotos方法,替換為下面內容:

//獲取圖片 - (void)refreshPhotos { //網絡家提示開啟 [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES]; //dropbox的API,這個接口用於搜索圖片 NSString *photoDir = [NSString stringWithFormat:@"https://api.dropbox.com/1/search/dropbox/%@/photos?query=.jpg",appFolder]; NSURL *url = [NSURL URLWithString:photoDir]; //啟動一個數據下載任務 [[_session dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { if (!error) { NSHTTPURLResponse *httpResp = (NSHTTPURLResponse*) response; //狀態碼為200請求成功 if (httpResp.statusCode == 200) { //返回的數據類型為json數組,解析 NSError *jsonError; NSArray *filesJSON = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:&jsonError]; NSMutableArray *dbFiles = [[NSMutableArray alloc] init]; if (!jsonError) { for (NSDictionary *fileMetadata in filesJSON) { DBFile *file = [[DBFile alloc] initWithJSONData:fileMetadata]; [dbFiles addObject:file]; } [dbFiles sortUsingComparator:^NSComparisonResult(id obj1, id obj2) { return [obj1 compare:obj2]; }]; //添加到數組中保存 _photoThumbnails = dbFiles; //更新主界面 dispatch_async(dispatch_get_main_queue(), ^{ [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO]; [self.tableView reloadData]; }); } } else { // 處理相應失敗// } } else { // 這里處理失敗 // } }] resume]; }
4.休息一下,運行看看,應該可以看到圖片加載了,我的效果如圖:
好了,現在我做上傳圖片的操作,並通過NSURLSessionDelegate和NSURLSessionTaskDelegate來幫助我們了解上傳的狀態和進度。
5.修改PhotosViewController.m使其遵守NSURLSessionTaskDelegate代理協議,代碼如圖:
@interface PhotosViewController ()<UITableViewDelegate, UITableViewDataSource, UIImagePickerControllerDelegate, UINavigationControllerDelegate, NSURLSessionTaskDelegate>
6.添加如下屬性,用於保存上傳任務:
@property (nonatomic, strong) NSURLSessionUploadTask *uploadTask;
7.修改uploadImage:方法,替代為如下內容:

- (void)uploadImage:(UIImage*)image { NSData *imageData = UIImageJPEGRepresentation(image, 0.6); // 配置一次只能對服務器一個連接 NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration]; config.HTTPMaximumConnectionsPerHost = 1; [config setHTTPAdditionalHeaders:@{@"Authorization": [Dropbox apiAuthorizationHeader]}]; // 初始化上傳會話 NSURLSession *upLoadSession = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil]; // 上傳任務的URL地址 NSURL *url = [Dropbox createPhotoUploadURL]; NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url]; [request setHTTPMethod:@"PUT"]; // 設置上傳的圖片數據 self.uploadTask = [upLoadSession uploadTaskWithRequest:request fromData:imageData]; // 上傳進度 self.uploadView.hidden = NO; [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES]; // 啟動任務 [_uploadTask resume]; }
8.最類最后面添加NSURLSessionTaskDelegate的兩個方法實現:

#pragma mark - NSURLSessionTaskDelegate 方法 // /** *這個代理方法可以跟蹤進度 * * @param session 會話 * @param task 任務 * @param bytesSent 正在接受到的數據大小 * @param totalBytesSent 總的接受的數據帶大小 * @param totalBytesExpectedToSend 估算總共需要接受的數據大小 */ - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesSent totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend { //根據接收到數據大小和總的數據大小計算出進度條顯示的進度值 dispatch_async(dispatch_get_main_queue(), ^{ [_progress setProgress: (double)totalBytesSent / (double)totalBytesExpectedToSend animated:YES]; }); } /** *當上傳或下載數據成功時執行 * * @param session 會話 * @param task 任務 * @param error 錯誤 */ - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error { // 主線程更新進度,隱藏加載提示,隱藏上傳進度 dispatch_async(dispatch_get_main_queue(), ^{ [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO]; _uploadView.hidden = YES; [_progress setProgress:0.5]; }); if (!error) { // 2 dispatch_async(dispatch_get_main_queue(), ^{ [self refreshPhotos]; }); } else { //處理錯誤 } }
9.找到cancelUpload:方法,當我們點擊cancel取消時會調用此方法,這里我們需要取消上傳任務:
// 停止上傳 - (IBAction)cancelUpload:(id)sender { //取消上傳任務 if (_uploadTask.state == NSURLSessionTaskStateRunning) { [_uploadTask cancel]; } }
10.哈哈,大功告成,運行一下看看效果吧
至此我通過三篇文章介紹了NSULRSession套件的原理,和它的常用類,然后通過一個實戰例子告訴大家NSURLSession如何使用。謝謝大家!