iOS多線程和網絡

提供的功能

  • 通過URL將數據下載到內存
  • 通過URL將數據下載到文件系統
  • 將數據上傳到指定的URL
  • 在後臺完成上述操作(支持後臺網絡操作,除非用戶強行關閉應用程序)

NSURLConnection和NSURLSession的關係示意圖

在這裏插入圖片描述
NSURLSession中的請求都看做一個請求任務(task)。

NSURLSession的Task繼承關係如下:
在這裏插入圖片描述

  • NSURLSessionTask 是一個抽象類,提供了一些基本的方法
  • NSURLSessionDataTask 是一個具體的 task 類,可以獲取數據
  • NSURLSessionDownloadTask 是一個具體的 task 類,可以下載數據
  • NSURLSessionUploadTask 是一個具體的 task 類,可以上傳數據
  • NSURLSessionStreamTasj 是一個具體的 task 類,以流的方式請求數據,使用較少

每一個類都有對應的協議。

代碼體現

基本使用

- (void)sesssion {    
    // 1. url    
    NSURL *url = [NSURL URLWithString:@"http://127.0.0.1/demo.json"];    
    // 2. 如果使用 GET 請求,request 可以省略    
    // session - 蘋果爲了方便程序員的開發,提供了一個全局的 session 單例 (我們是直接獲取的,不需要創建)    
    NSURLSession *session = [NSURLSession sharedSession];    
    // 3. 數據任務 - 所有的任務,都是由session 發起的    
    NSURLSessionDataTask *task = [session dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {       
        // 4.將得到數據進行返序列化        
        NSLog(@"%@", [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL]);    	}];    
    // 5. 啓動任務(一定要啓動任務)    
    [task resume];
}

注意以上發起的DataTask任務中:

- (NSURLSessionDataTask *)dataTaskWithURL:(NSURL *)url completionHandler:(void (^)(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error))completionHandler;

  • 該方法中會自動將 url 轉換爲一個請求對象(NSURLRequest),並且該請求對象是 GET 請求方式
  • 回調方法是在子線程中運行的,所以如果在回調方法中刷新 UI 必須回到主線程中
  • 回調方法中有兩個參數 response / error,這兩個參數和 該消息的接受者(即 NSURLSessionDataTask 對象)中的 response / error 是指同一個內容,即地址相同
  • 使用該方法的缺點是不能監聽獲取數據的進度,因爲只有當全部請求完數據後,纔會調用這個方法,也就是說,data 中的數據是請求的全部數據.

// @param request 請求對象

- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request completionHandler:(void (^)(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error))completionHandler;

withRequest方法可以手動設置請求對象,這樣就可以指定GET/POST中的任何一種方法了。而withURL方法只能使用GET方法。

在第三方框架對於 NSURLSession 的基本的封裝思路就是這樣,給一個 url ,後直接啓動就可以了。

- (void)sesssionDemo {    
    // 1. url    
    NSURL *url = [NSURL URLWithString:@"http://127.0.0.1/demo.json"];    
    // 2. 啓動會話    
    [self taskWithURL:url];
}

- (void)taskWithURL:(NSURL *)url {    
    // 1. 全局會話發起數據任務 - 所有的任務,都是由session 發起的    
    [[[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {        
        NSLog(@"%@", [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL]);    }] resume];
}

但是使用單例類的缺點就是不能實時監控獲取數據的情況。因爲以上兩種方法中只有當請求完全部的數據後纔會調用回調方法,如果想要實時獲取並監控請求進度,就要設置代理方法。

NSURLSession下載進度跟進

下載進度的跟進是通過代理實現的。

需要跟蹤進度,就需要使用代理,代理是一對一的關係,我就要使用一個自定義的會話。

@property(strong, nonatomic)NSURLSession *session;
  • 注意delegate Queue如果是傳代理方法在子線程中運行時,則UI的刷新要注意在主線程進行。

  • 而主隊列是[NSOperationQueue mainQueue]

  • 如何選擇隊列: 網絡訪問結束後,如果不需要做複雜的操作,可以指定主隊列,這樣不用考慮線程間通訊。

  • 由於下載本身是異步的,不會阻塞主線程工作。

  • 在主線程中代理的話,所有的代理回調,都是在主線程異步的,不會阻塞主線程執行!(主線程異步並不會開闢新的線程, 只是把隊列任務放到異步執行, 等待主隊列中的當前任務都完成, 主線程空閒的時候, 再來執行需要執行的任務)。

// @param configuration 配置信息對象
// @param delegate 代理對象
// @param queue 代理方法在哪個線程中運行,如果傳 nil 則會在子線程中運行代理方法
+ (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration delegate:(nullable id <NSURLSessionDelegate>)delegate delegateQueue:(nullable NSOperationQueue *)queue;

- (void)sessionDownload {    
// 1. url    
NSURL *url = [NSURL URLWithString:@"http://127.0.0.1/123.mp4"];    
// 2. 下載    
// 進度跟進:代理,蘋果的頭文件中,代理都是定義在後面的    
// 注意:如果要跟進下載進度,不能使用全局會話    
// 這個方法是和下面那個方法是矛盾的,使用了這個方法下面那個必須實現的代理方法是不能使用的    
//    
[[self.session downloadTaskWithURL:url completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {    
    //        
    // 這個是一個完成的輸出    
    //        
    NSLog(@"%@ %@", location.path, [NSThread currentThread]);    
    //    
}] resume];    

要想使用下面的代理方法的話:直接使用如下方法下載數據。

// 直接啓動任務,可以通過代理跟蹤進度    
[[self.session downloadTaskWithURL:url] resume];}

以下是必須實現的協議方法,表示下載完成了

  • location表示文件存儲的路徑
 - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location{
 };

以下是斷點續傳,可以什麼都不寫,這個是用來實現下載跟進的

在恢復下載的時候可以調用該方法:

  • fileOffset表示從什麼地方開始下載
  • expectedTotalBytes表示文件的總大小。
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes{
};

以下方法是用來進行下載跟進的 ,下載進度的跟進存在的問題,存在一個峯值的問題。

  • downloadTask 表示下載任務

  • bytesWritten 表示本次寫入的數據大小

  • totalBytesWritten 下載的數據總大小

  • totalBytesExpectedToWrite 文件的總大小

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite{    // 下載的進度    
    float progress = (float)totalBytesWritten / totalBytesExpectedToWrite;    
    // 打印下載的進度   
    NSLog(@"progress = %f",progress);
}

NSURLSession的斷點續傳問題

斷點續傳主要考慮的是將會話暫停繼續還是將任務進行暫停繼續。

NSURLSession 提供的斷點續傳存在的問題,不能"停",應用程序不能終止,一旦終止,續傳數據就丟失了,下次還需要重頭再來!

@property (nonatomic, strong) NSURLSession *session;/// 下載任務

@property (nonatomic, strong) NSURLSessionDownloadTask *task;/// 續傳數據
  • 啓動下載任務
- (IBAction)start {
	// 1. url    
    NSURL *url = [NSURL URLWithString:@"http://dldir1.qq.com/qqfile/QQforMac/QQ_V4.0.2.dmg"];
    // 發起下載會話   
	self.task = [self.session downloadTaskWithURL:url]; 
	 // 啓動任務
	[self.task resume]; 
}
  • 暫停下載任務
/// 暫停 - 下載任務
- (IBAction)pause {    
    NSLog(@"暫停");    
    // 使用需要續傳的數據,暫停下載任務    
    // 暫停方法,只需要執行一次,被暫停就不用再次暫停    
    /**     1. 所有的任務都是由會話發起的     
    		2. 當任務啓動後,會話會對任務進行強引用     
    		3. 一旦任務被取消,意味着會話不再引用任務,一個 weak 對象沒有其他對象強引用,會被立即釋放     
    		4. 如果屬性 是 strong,除了會話之外,vc同樣對任務強引用(session 爲什麼要使用強引用)     
    */     
    // 這個是取消下載任務 (達到暫停的目的)    
    [self.task cancelByProducingResumeData:^(NSData *resumeData) {        
        NSLog(@"---> %tu", resumeData);        
        // 記錄續傳數據        
        self.resumeData = resumeData; 
        // 將暫停時候的數據保存起來(如果不保存下次啓動的時候是沒有數據的)        
        // 釋放了下載任務        
        self.task = nil; 
        // 將下載任務釋放避免多次暫停的問題    
    }];
}
  • 繼續下載任務
- (IBAction)resume {    
    // 需要避免下載任務重複創建    
    if (self.resumeData == nil) {        
        NSLog(@"沒有續傳數據");        
        return;    
    }    
    // 使用續傳數據,重新實例化下載任務    
    // 一旦續傳的下載任務被建立之後,續傳數據就沒有用了!    
    self.task = [self.session downloadTaskWithResumeData:self.resumeData];    				self.resumeData = nil;    
    [self.task resume];
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章