今天去面試,被問了一道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 總共只需要三步:
- 創建NSURLSession對象
- 通過 NSURLSession 的實例創建 Task
- 執行 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的頭文件中的這些方法,如圖:

從這些方法中得知,分別返回了NSURLSessionDataTask
、NSURLSessionUploadTask
、NSURLSessionDownloadTask
,NSURLSessionStreamTask
這四個類的對象,那么這四個類是干什么呢?我們接著往下看。
URLSessionTask
NSURLSessionTask是一個抽象類,其下有4個實體子類可以直接使用:NSURLSessionDataTask
、NSURLSessionUploadTask
、NSURLSessionDownloadTask
、NSURLSessionStreamTask
。這四個子類封裝了現代程序四個最基本的網絡任務:獲取數據,比如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
,它有四個直接或間接子協議NSURLSessionTaskDelegate
,NSURLSessionDownloadDelegate
和 NSURLSessionStreamDelegate
、NSURLSessionDataDelegate
。具體代理方法如下:
//創建有代理的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