NSURLSession與NSURLConnection區別

今天去面試,被問了一道AFNetworking2.0和3.0有什么區別,當時心想,這誰不知道啊,隨口答到:2.0使用的NSURLConnection,3.0使用的是NSURLSession,人家又問NSURLSesstion和NSURLConnection有什么區別,瞬間傻眼了,支支吾吾只是說出了緩存策略的不同。亡羊補牢,為時未晚,這篇博客就講解一下它們兩者到底有什么區別。

使用現狀

NSURLSession是NSURLConnection的替代者,在2013年蘋果全球開發者大會上(WWDC2013)隨iOS7一起發布的,是對NSURLConnection進行了重構優化后的新的網絡接口。從iOS9開始,NSURLConnection中發送請求的兩個方法已經過期(同步請求,異步請求),初始化網絡連接的方法也被設置為過期,系統不再推薦使用,建議使用NSURLSession發送網絡請求。NSURLConnection被廢棄的主要接口:

- (nullable instancetype)initWithRequest:(NSURLRequest *)request delegate:(nullable id)delegate startImmediately:(BOOL)startImmediately NS_DEPRECATED(10_5, 10_11, 2_0, 9_0, "Use NSURLSession (see NSURLSession.h)") __WATCHOS_PROHIBITED;

- (nullable instancetype)initWithRequest:(NSURLRequest *)request delegate:(nullable id)delegate NS_DEPRECATED(10_3, 10_11, 2_0, 9_0, "Use NSURLSession (see NSURLSession.h)") __WATCHOS_PROHIBITED;
+ (nullable NSURLConnection*)connectionWithRequest:(NSURLRequest *)request delegate:(nullable id)delegate NS_DEPRECATED(10_3, 10_11, 2_0, 9_0, "Use NSURLSession (see NSURLSession.h)") __WATCHOS_PROHIBITED;
//異步請求
+ (void)sendAsynchronousRequest:(NSURLRequest*) request
                          queue:(NSOperationQueue*) queue
              completionHandler:(void (^)(NSURLResponse* __nullable response, NSData* __nullable data, NSError* __nullable connectionError)) handler NS_DEPRECATED(10_7, 10_11, 5_0, 9_0, "Use [NSURLSession dataTaskWithRequest:completionHandler:] (see NSURLSession.h") __WATCHOS_PROHIBITED;
//同步請求             
+ (nullable NSData *)sendSynchronousRequest:(NSURLRequest *)request returningResponse:(NSURLResponse * __nullable * __nullable)response error:(NSError **)error NS_DEPRECATED(10_3, 10_11, 2_0, 9_0, "Use [NSURLSession dataTaskWithRequest:completionHandler:] (see NSURLSession.h") __WATCHOS_PROHIBITED;

普通任務和上傳

NSURLSession針對下載/上傳等復雜的網絡操作提供了專門的解決方案,針對普通、上傳和下載分別對應三種不同的網絡請求任務:NSURLSessionDataTask,NSURLSessionUploadTask和NSURLSessionDownloadTask。創建的task都是掛起狀態,需要resume才能啟動。

當服務器返回的數據較小時,NSURLSession與NSURLConnection執行普通任務的操作步驟沒有區別。
執行上傳任務時,NSURLSession與NSURLConnection一樣需要設置POST請求的請求體進行上傳。

下載任務方式

NSURLConnection下載文件時,先是將整個文件下載到內存,然后再寫入到沙盒,如果文件比較大,就會出現內存暴漲的情況。

而使用NSURLSessionUploadTask下載文件,會默認下載到沙盒中的tem文件中,不會出現內存暴漲的情況,但是在下載完成后會把tem中的臨時文件刪除,需要在初始化任務方法時,在completionHandler回調中增加保存文件的代碼。(后面會詳細說)

請求方法的控制

NSURLConnection實例化對象,實例化開始,默認請求就發送(同步發送),不需要調用start方法。而cancel可以停止請求的發送,停止后不能繼續訪問,需要創建新的請求。

NSURLSession有三個控制方法,取消(cancel)、暫停(suspend)、繼續(resume),暫停以后可以通過繼續恢復當前的請求任務。

斷點續傳的方式

NSURLConnection進行斷點下載,通過設置訪問請求的HTTPHeaderField的Range屬性,開啟運行循環,NSURLConnection的代理方法作為運行循環的事件源,接收到下載數據時代理方法就會持續調用,并使用NSOutputStream管道流進行數據保存。

NSURLSession進行斷點下載,當暫停下載任務后,如果downloadTask(下載任務)為非空,調用cancelByProducingResumeData:(void (^)(NSData *resumeData))completionHandler這個方法,這個方法接收一個參數,完成處理代碼塊,這個代碼塊有一個NSData參數resumeData,如果resumeData非空,我們就保存這個對象到視圖控制器的resumeData屬性中,在點擊再次下載時,通過調用[ [self.session downloadTaskWithResumeData:self.resumeData]resume]方法進行繼續下載操作

經過以上比較可以發現,使用NSURLSession進行斷點下載更加便捷.

配置信息

NSURLSession的構造方法(sessionWithConfiguration:delegate:delegateQueue)中有一個NSURLSessionConfiguration類的參數可以設置配置信息,其決定了cookie,安全和高速緩存策略,最大主機連接數,資源管理,網絡超時等配置。NSURLConnection不能進行這個配置,相比較與NSURLConnection依賴與一個全局的配置對象,缺乏靈活性而言,NSURLSession有很大的改進了。(關于配置信息,后面會講解到)

通過以上幾點,大概知道了NSURLSession和NSURLConnection的區別,想必下載再遇到這樣的問題不會支支吾吾了。下面來點NSURLSession的干貨。


干貨開始


NSURLSession

NSURLSession 為 HTTP 數據傳輸提供一系列的接口,而使用 NSURLSession 總共只需要三步:

  1. 創建NSURLSession對象
  2. 通過 NSURLSession 的實例創建 Task
  3. 執行 Task
如何獲取Session對象

1.獲取默認的 Session 對象

/*
 * 用于基本的網絡請求,可以幾行代碼就獲取 URL 的內容,使用簡單
 * 無法不斷的獲取服務器返回的數據
 * 無法修改默認的連接行為
 * 身份驗證的能力有限
 * 任務在后臺時無法上傳和下載
 */
+ (NSURLSession *)sharedSession;

2.自定義 Session 對象

 // 不用代理
+ (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration;
// 用代理
+ (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration
                                  delegate:(nullable id <NSURLSessionDelegate>)delegate
                             delegateQueue:(nullable NSOperationQueue *)queue;

在使用自定義方式創建NSURLSession對像時,都需要傳入一個NSURLSessionConfiguration參數,這個參數是對Session的網絡請求的基本配置。那這個NSURLSessionConfiguration都有哪些配置呢?接著往下看

NSURLSessionConfiguration

有三個方法來創建NSURLSessionConfiguration:

  • defaultSessionConfiguration 使用全局的cache,cookie,使用硬盤來緩存數據。
  • ephemeralSessionConfiguration 臨時session配置,與默認配置相比,這個配置不會將緩存、cookie等存在本地,只會存在內存里,所以當程序退出時,所有的數據都會消失
  • backgroundSessionConfiguration 后臺session配置,與默認配置類似,不同的是會在后臺開啟另一個線程來處理網絡數據。

一旦創建了NSURLSessionConfiguration就可以給它設置各種屬性

看NSURLSessionConfiguration的頭文件:

@interface NSURLSessionConfiguration : NSObject <NSCopying>

/* 三種創建方式 */

+ (NSURLSessionConfiguration *)defaultSessionConfiguration;
+ (NSURLSessionConfiguration *)ephemeralSessionConfiguration;
+ (NSURLSessionConfiguration *)backgroundSessionConfigurationWithIdentifier:(NSString *)identifier NS_AVAILABLE(10_10, 8_0);

/* 當使用上述第三種方式創建后臺sessionConfiguration時可以讀到初始化時傳入的唯一標識,其他創建方式都為空 */
@property (nullable, readonly, copy) NSString *identifier;

/* 
緩存策略,默認值是NSURLRequestUseProtocolCachePolicy
 */
@property NSURLRequestCachePolicy requestCachePolicy;

/* 給request指定每次接收數據超時間隔,如果下一次接受新數據用時超過該值,則發送一個請求超時給該request。默認為60s */
@property NSTimeInterval timeoutIntervalForRequest;

/* 給指定resource設定一個超時時間,resource需要在時間到達之前完成。默認是7天。 */
@property NSTimeInterval timeoutIntervalForResource;

/* 指定網絡傳輸類型。精切指出傳輸類型,可以讓系統快速響應,提高傳輸質量,延長電池壽命等。
typedef NS_ENUM(NSUInteger, NSURLRequestNetworkServiceType)
{
    NSURLNetworkServiceTypeDefault = 0, // 普通網絡傳輸,默認使用這個
    NSURLNetworkServiceTypeVoIP = 1,    // 網絡語音通信傳輸,只能在VoIP使用
    NSURLNetworkServiceTypeVideo = 2,   // 影像傳輸
    NSURLNetworkServiceTypeBackground = 3, // 網絡后臺傳輸,優先級不高時可使用。對用戶不需要的網絡操作可使用
    NSURLNetworkServiceTypeVoice = 4       // 語音傳輸
};
 */
@property NSURLRequestNetworkServiceType networkServiceType;

/* 是否使用蜂窩網絡,默認是yes. */
@property BOOL allowsCellularAccess;

/* 是否由系統根據性能自動裁量后臺任務。默認值是NO。同sessionSendsLaunchEvent一樣,只對后臺configuration有效。 */
@property (getter=isDiscretionary) BOOL discretionary NS_AVAILABLE(10_10, 7_0);

/* 
如果要為app的插件提供session,需要給這個值賦值
 */
@property (nullable, copy) NSString *sharedContainerIdentifier NS_AVAILABLE(10_10, 8_0);

/* 
 表示當后臺傳輸結束時,是否啟動app.這個屬性只對 后臺sessionConfiguration 生效,其他configuration類型會自動忽略該值。默認值是YES。
 */
@property BOOL sessionSendsLaunchEvents NS_AVAILABLE(NA, 7_0);

/* 
指定了會話連接中的代理服務器。同樣地,大多數面向消費者的應用程序都不需要代理,所以基本上不需要配置這個屬性,默認為NULL
*/
@property (nullable, copy) NSDictionary *connectionProxyDictionary;

/* 確定是否支持SSLProtocol版本的會話
 */
@property SSLProtocol TLSMinimumSupportedProtocol;

/* 
確定是否支持SSLProtocol版本的會話
*/
@property SSLProtocol TLSMaximumSupportedProtocol;

/* 
它可以被用于開啟HTTP管道,這可以顯著降低請求的加載時間,但是由于沒有被服務器廣泛支持,默認是禁用的
 */
@property BOOL HTTPShouldUsePipelining;

/* 
默認為yes,是否提供來自shareCookieStorge的cookie,如果想要自己提供cookie,可以使用HTTPAdditionalHeaders來提供。
 */
@property BOOL HTTPShouldSetCookies;

/* Policy for accepting cookies.  This overrides the policy otherwise specified by the cookie storage. */
@property NSHTTPCookieAcceptPolicy HTTPCookieAcceptPolicy;

/* 
指定了一組默認的可以設置出站請求的數據頭。這對于跨會話共享信息,如內容類型,語言,用戶代理,身份認證,是很有用的。
例如:
    @{@"Accept": @"application/json",
     @"Accept-Language": @"en",
     @"Authorization": authString,
     @"User-Agent": userAgentString
   }
 */
@property (nullable, copy) NSDictionary *HTTPAdditionalHeaders;

/* 
同時連接一個host的最大數。iOS默認是4.APP是作為一個整體來看的
 */
@property NSInteger HTTPMaximumConnectionsPerHost;

/* 
存儲cookie,清除存儲,直接set為nil即可。
對于默認和后臺的session,使用sharedHTTPCookieStorage。
對于短暫的session,cookie僅僅儲存到內存,session失效時會自動清除。
 */
@property (nullable, retain) NSHTTPCookieStorage *HTTPCookieStorage;

/* 
證書存儲,如果不使用,可set為nil.
默認和后臺session,默認使用的sharedCredentialStorage.
短暫的session使用一個私有存儲在內存中。session失效會自動清除。
 */
@property (nullable, retain) NSURLCredentialStorage *URLCredentialStorage;

/* 
緩存NSURLRequest的response。
默認的configuration,默認值的是sharedURLCache。
后臺的configuration,默認值是nil
短暫的configuration,默認一個私有的cache于內存,session失效,cache自動清除。
*/
@property (nullable, retain) NSURLCache *URLCache;

/* Enable extended background idle mode for any tcp sockets created.    Enabling this mode asks the system to keep the socket open
 *  and delay reclaiming it when the process moves to the background (see https://developer.apple.com/library/ios/technotes/tn2277/_index.html) 
 */
@property BOOL shouldUseExtendedBackgroundIdleMode NS_AVAILABLE(10_11, 9_0);

/* 
處理NSURLRequest的NSURLProtocol的子類。
重要:對后臺Session失效。
 */
@property (nullable, copy) NSArray<Class> *protocolClasses;

@end

現在,我們知道如何來創建一個Session對象了,創建完Session對象,根據一個Request對象我們就可以發送網絡請求了。下面看一下NSURLSession的頭文件中的這些方法,如圖:

從這些方法中得知,分別返回了NSURLSessionDataTaskNSURLSessionUploadTaskNSURLSessionDownloadTaskNSURLSessionStreamTask 這四個類的對象,那么這四個類是干什么呢?我們接著往下看。

URLSessionTask

NSURLSessionTask是一個抽象類,其下有4個實體子類可以直接使用:NSURLSessionDataTaskNSURLSessionUploadTaskNSURLSessionDownloadTaskNSURLSessionStreamTask。這四個子類封裝了現代程序四個最基本的網絡任務:獲取數據,比如JSON或者XML,上傳文件和下載文件還有數據流的獲取。

NSURLSession比NSURLConnection最方便的地方就是任務可以暫停,繼續。在網絡請求中,真正去執行下載或者上傳任務的就是URLSessionTask,我們來看一下它常用的方法:

- (void)resume; 當使用NSURLSession創建一個NSURLSessionTask任務時,要手動調用此方法,任務才會開啟,而NSURLConnection默認開啟。

- (void)suspend; 暫停任務方法,手動調用會暫停當前任務,再次開啟此任務時,會從緊接上次任務開始,會面會說到如何暫停任務再開啟任務。

- (void)cancel; 取消任務。

NSURLSessionTask還有個屬性,@property (readonly) NSURLSessionTaskState state; 此屬性標識當前任務的狀態,枚舉類型

typedef NS_ENUM(NSInteger, NSURLSessionTaskState) {
    NSURLSessionTaskStateRunning = 0,                     /* 正在執行 */
    NSURLSessionTaskStateSuspended = 1,                   /* 暫停狀態 */
    NSURLSessionTaskStateCanceling = 2,                   /* 取消狀態*/
    NSURLSessionTaskStateCompleted = 3,                   /* 任務完成狀態 */
}

上面說到的四個類,都直接或間接繼承NSURLSessionTask,所有NSURLSessionTask的方法或者屬性這四個類都有,那么,簡單說一下這四個類都是干什么的。

NSURLSessionDataTask

NSURLSessionDataTask是開發中使用頻率最高的,我們平常使用的GET和POST請求都是通過它來實現的,如果請求的數據簡單并且不需要對獲取的數據進行復雜操作,我們使用 Block 解析返回的數據即可。具體代碼如下:

簡單 Get 請求
/**
 *  簡單 GET 請求
  */
- (void)getWithsharedSession
{
  // 獲取默認 Session
  NSURLSession *session = [NSURLSession sharedSession];
  // 創建 URL
  NSURL *url = [NSURL URLWithString:@"https://www.baidu.com/s?wd=test"];
  // 創建任務 task
  NSURLSessionDataTask *task = [session dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
    // 獲取數據后解析并輸出
     NSString *dataStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    NSLog(@"%@",dataStr);
  }];
  // 啟動任務
  [task resume];
}
簡單 POST 請求
/**
 *  簡單 Post 請求,POST 和 GET 請求在于對 request 的處理不同,其余和 GET 相同
 */
- (void)postWithSharedSession
{
  // 獲取默認 Session
  NSURLSession *session = [NSURLSession sharedSession];
  // 創建 URL
  NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/login"];
  // 創建 request
  NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
  // 請求方法
  request.HTTPMethod = @"POST";
  // 請求體
  request.HTTPBody = [@"username=1234&pwd=4321" dataUsingEncoding:NSUTF8StringEncoding];
  // 創建任務 task
  NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        // 獲取數據后解析并輸出
        NSLog(@"%@",[NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil]);
  }];
  // 啟動任務
  [task resume];
}

另外我們也可以設置session的代理來實時的監聽數據,我們可以使用NSURLSession的+ (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration;+ (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration delegate:(nullable id <NSURLSessionDelegate>)delegate delegateQueue:(nullable NSOperationQueue *)queue;這兩個方法來設置代理,具體的協議為NSURLSessionDelegate,它有四個直接或間接子協議NSURLSessionTaskDelegateNSURLSessionDownloadDelegateNSURLSessionStreamDelegateNSURLSessionDataDelegate。具體代理方法如下:

//創建有代理的session
- (void)sessionDataDelegate
{
    // 創建帶有代理方法的自定義 session
    NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]];
    
    // 創建任務
    NSURLSessionDataTask *task = [session dataTaskWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://120.25.226.186:32812/login?username=1234&pwd=4321"]]];
    
    // 啟動任務
    [task resume];
}

#pragma mark -
#pragma mark - NSURLSessionDelegate

// 1. 接收到服務器的響應
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler
{
    NSLog(@"接收到服務器的響應");
    // 必須設置對響應進行允許處理才會執行后面兩個操作。
    completionHandler(NSURLSessionResponseAllow);
}

// 2. 接收到服務器的數據(可能調用多次)
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
    // 處理每次接收的數據
    NSLog(@"接受到服務器的數據%s",__func__);
}

// 3. 請求成功或者失敗(如果失敗,error有值)
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
    // 請求完成,成功或者失敗的處理
    NSLog(@"SessionTask %s",__func__);
}

NSURLSessionDownloadTask

NSURLSessionDownloadTask在下載文件的時候,是將數據一點點地寫入本地的臨時文件。所以在 completionHandler 這個 block 里,我們需要把文件從一個臨時地址移動到一個永久的地址保存起來:

/**
 *  NSURLSessionDownloadTask 下載任務
 */
- (void)downLoad
{
    NSURLSession *session = [NSURLSession sharedSession];
    NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/resources/images/minion_01.png"] ;
    NSURLSessionDownloadTask *task = [session downloadTaskWithURL:url completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {
        // location 是沙盒中 tmp 文件夾下的一個臨時 url,文件下載后會存到這個位置,由于 tmp 中的文件隨時可能被刪除,所以我們需要自己需要把下載的文件挪到 Caches 文件夾中
        NSString *path = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:response.suggestedFilename];
        // 剪切文件
        [[NSFileManager defaultManager] moveItemAtURL:location toURL:[NSURL fileURLWithPath:path] error:nil];
        NSLog(@"%@",[NSThread currentThread]);
        //切記當前為子線程,
        dispatch_async(dispatch_get_main_queue(), ^{
            self.imageView.image = [UIImage imageNamed:path];
        });
        
    }];
    // 啟動任務
    [task resume];
}

代理方法下載

/**
 *  NSURLSessionDownloadTask 代理
 */
- (void)sessionDownloadTaskDelegate
{
  // 創建帶有代理方法的自定義 session
  NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]];

  // 創建任務
  NSURLSessionDownloadTask *task = [session downloadTaskWithURL:[NSURL URLWithString:@"http://120.25.226.186:32812/resources/images/minion_02.png"]];

  // 啟動任務
  [task resume];
}
#pragma mark -
#pragma mark -NSURLSessionDownloadDelegate
/**
 *  寫入臨時文件時調用
 *  @param bytesWritten              本次寫入大小
 *  @param totalBytesWritten         已寫入文件大小
 *  @param totalBytesExpectedToWrite 請求的總文件的大小
 */
- (void)URLSession:(NSURLSession *)session downloadTask:(nonnull NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
    //可以監聽下載的進度
  CGFloat progress = 1.0 * totalBytesWritten / totalBytesExpectedToWrite;
  NSLog(@"downloadTask %f",progress);
}

// 2. 下載完成調用
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location
{
  // location 還是一個臨時路徑,需要自己挪到需要的路徑(caches 文件夾)
  NSString *filePath = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:downloadTask.response.suggestedFilename];
  [[NSFileManager defaultManager] moveItemAtURL:location toURL:[NSURL fileURLWithPath:filePath] error:nil];
  NSLog(@"downloadTask 移動文件路徑");
}
斷點續傳

說一下開發中經常用到的斷點續傳。在開發中,我們經常由于某種原因,在下載或上傳的時候往往不能一次性下載或上傳完,有可能下載或上傳了一半就終止了,這時候當條件滿足繼續下載或上傳時,我們不希望從頭開始,這時候就可以使用斷點續傳。它的大概思路是:

  • 某種限制,續傳暫停
  • 將暫停后數據(當前數據)保存起來--_resumeData = resumeData;
  • 條件允許續傳時,使用resumeData創建新的NSURLSessionTask

代碼:

- (IBAction)startDowning:(id)sender {
    

    if (_resumeData) {
        _downloadTask = [_session downloadTaskWithResumeData:_resumeData];
    }else {
        _session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:nil];
        _request = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:@"http://image.baidu.com/search/down?tn=download&ipn=dwnl&word=download&ie=utf8&fr=result&url=http%3A%2F%2Fb.zol-img.com.cn%2Fdesk%2Fbizhi%2Fimage%2F6%2F960x600%2F1427787678554.jpg&thumburl=http%3A%2F%2Fimg3.imgtn.bdimg.com%2Fit%2Fu%3D1996019669%2C1779575266%26fm%3D21%26gp%3D0.jpg"]];
        _downloadTask = [_session downloadTaskWithRequest:_request];
    }
    [_downloadTask resume];
}

- (IBAction)stopDowning:(id)sender {
    if (_downloadTask) {
        __weak typeof (self)weakSelf = self;
        [_downloadTask cancelByProducingResumeData:^(NSData * _Nullable resumeData) {
            NSLog(@"%@",resumeData);
            weakSelf.resumeData = resumeData;
            weakSelf.downloadTask = nil;
        }];
    }
}

#pragma mark -
#pragma mark - NSURLSessionDownloadDelegate

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location {
    NSString *path = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:downloadTask.response.suggestedFilename];
    // 剪切文件
    [[NSFileManager defaultManager] moveItemAtURL:location toURL:[NSURL fileURLWithPath:path] error:nil];
    
    dispatch_async(dispatch_get_main_queue(), ^{
        self.imageView.image = [UIImage imageNamed:path];
    });
}
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
      didWriteData:(int64_t)bytesWritten
 totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite {
    CGFloat progress = 1.0 * totalBytesWritten / totalBytesExpectedToWrite;
    NSLog(@"downloadTask %f",progress);
    dispatch_async(dispatch_get_main_queue(), ^{
        self.progressView.progress = progress;
    });
    
}

注意:上面的代碼是不會斷點續傳的,原因是這個圖片的url不支持斷點續傳,在斷點續傳時,要和服務器配合好。

NSURLSessionUploadTask

在 NSURLSession 中,文件上傳主要使用兩種方式:

- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromFile:(NSURL *)fileURL;
- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromData:(NSData *)bodyData;

我們這里使用第二個方法,表單的形式上傳數據

- (void)upload {
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"https://api.weibo.com/2/statuses/public_timeline.json"]];
    //設置HTTP請求方式  GET / POST
    [request setHTTPMethod:@"POST"];
    //設置請求頭
    NSString *boundary = @"hwg";
    [request setValue:[NSString stringWithFormat: @"multipart/form-data;%@", boundary]forHTTPHeaderField:@"Content-type"];
    //設置請求體
    //獲取上傳的圖片的data
    NSData *data = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"xiaoxin" ofType:@"jpeg"]];
    //此處添加需要看清楚內容
    NSData *body =  [self httpFormDataBodyWithBoundary:boundary params:@{@"access_token":@"2.00cYYKWF6EKpiB3883361b1dJiZ4eD",@"status":@"哈哈,這是我測試NSURLSession上傳文件的微博"} fieldName:@"pic" fileName:@"pic.png" fileContentType:@"image/png" data:data];
    NSURLSession *session = [NSURLSession sharedSession];
    NSURLSessionUploadTask *upload_task = [session uploadTaskWithRequest:request fromData:body completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        NSLog(@"upload  success");
    }];
    //必須要 resume
    [upload_task resume];
}

#pragma mark-拼接請求體
- (NSData *)httpFormDataBodyWithBoundary:(NSString *)boundary
                                 params:(NSDictionary *)params
                              fieldName:(NSString *)fieldName
                               fileName:(NSString *)fileName
                        fileContentType:(NSString *)fileContentType
                                   data:(NSData *)fileData {
    
    NSString *preBoundary = [NSString stringWithFormat:@"--%@",boundary];
    NSString *endBoundary = [NSString stringWithFormat:@"--%@--",boundary];
    NSMutableString *body = [[NSMutableString alloc] init];
    //遍歷
    for (NSString *key in params) {
        //得到當前的key
        //如果key不是當前的pic,說明value是字符類型,比如name:Boris
        //添加分界線,換行,必須使用\r\n
        [body appendFormat:@"%@\r\n",preBoundary];
        //添加字段名稱換2行
        [body appendFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n\r\n",key];
        //添加字段值
        [body appendFormat:@"%@\r\n",[params objectForKey:key]];
        
    }
    //添加分界線,換行
    [body appendFormat:@"%@\r\n",preBoundary];
    //聲明pic字段,文件名為boris.png
    [body appendFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"%@\"\r\n",fieldName,fileName];
    //聲明上傳文件的格式
    [body appendFormat:@"Content-Type: %@\r\n\r\n",fileContentType];
    //聲明結束符
    NSString *endStr = [NSString stringWithFormat:@"\r\n%@",endBoundary];
    //聲明myRequestData,用來放入http  body
    NSMutableData *myRequestData = [NSMutableData data];
    //將body字符串轉化為UTF8格式的二進制
    [myRequestData appendData:[body dataUsingEncoding:NSUTF8StringEncoding]];
    //將image的data加入
    [myRequestData appendData:fileData];
    //加入結束符--hwg--
    [myRequestData appendData:[endStr dataUsingEncoding:NSUTF8StringEncoding]];
    return myRequestData;
}

這里我們需要拼接一個表單數據,才能夠上傳數據。 當然,我們也可以用代理方法來監聽上傳的進度

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
   didSendBodyData:(int64_t)bytesSent
    totalBytesSent:(int64_t)totalBytesSent
totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend {
    CGFloat progress = 1.0 * totalBytesSent / bytesSent;
    NSLog(@"downloadTask %f",progress);
}

結語

以上就是我今天總結的,哪里有問題還希望大家提出意見。其實在開發中,我們很少使用到這些,因為總是有一些牛人為我們封裝了各種功能的強大庫,比如網絡類,最常用的就是AFNetworking。人家的庫為什么好用,說白了就是各種情況都考慮到了。所有我們要學的還是人家的編程思想。接下來,我會做一個專題,研究一下各大平常使用到的庫,它們到底牛在哪里,敬請閱讀!我的博客www.guiyongdong.com

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,030評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,310評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,951評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,796評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,566評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,055評論 1 322
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,142評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,303評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,799評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,683評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,899評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,409評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,135評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,520評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,757評論 1 282
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,528評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,844評論 2 372

推薦閱讀更多精彩內容