NSURLSession使用實戰教程


  我的前面兩篇文章介紹了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:方法,替換為下面內容:

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];

}
refreshPhotos:

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];
  
}
uploadImage:

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 {
        //處理錯誤
    }
}
NSURLSessionTaskDelegate 方法

9.找到cancelUpload:方法,當我們點擊cancel取消時會調用此方法,這里我們需要取消上傳任務:

// 停止上傳
- (IBAction)cancelUpload:(id)sender
{
    //取消上傳任務
    if (_uploadTask.state == NSURLSessionTaskStateRunning) {
        [_uploadTask cancel];
    }

}

10.哈哈,大功告成,運行一下看看效果吧

 

 

 

  至此我通過三篇文章介紹了NSULRSession套件的原理,和它的常用類,然后通過一個實戰例子告訴大家NSURLSession如何使用。謝謝大家!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM