(1)兩種爲NSURLConnection設置代理方式的區別數組
//第一種設置方式: //經過該方法設置代理,會自動的發送請求 // [[NSURLConnection alloc]initWithRequest:request delegate:self]; //第二種設置方式: //設置代理,startImmediately爲NO的時候,該方法不會自動發送請求 NSURLConnection *connect = [[NSURLConnection alloc]initWithRequest:request delegate:self startImmediately:NO]; //手動經過代碼的方式來發送請求 //注意該方法內部會自動的把connect添加到當前線程的RunLoop中在默認模式下執行 [connect start];
(2)如何控制代理方法在哪一個線程調用安全
//說明:默認狀況下,代理方法會在主線程中進行調用(爲了方便開發者拿到數據後處理一些刷新UI的操做不須要考慮到線程間通訊) //設置代理方法的執行隊列 [connect setDelegateQueue:[[NSOperationQueue alloc]init]];
(3)開子線程發送網絡請求的注意點,適用於自動發送網絡請求模式服務器
```objc
//使用GCD開啓一個子線程來發送網絡請求
dispatch_async(dispatch_get_global_queue(0, 0), ^{
//使用非自動發送網絡請求模式,發送請求OK
/
//建立NSURLConnection對象,設置代理,暫不發送
NSURLConnection connect = [[NSURLConnection alloc]initWithRequest:request delegate:self startImmediately:NO];
//設置代理方法的執行隊列
[connect setDelegateQueue:[[NSOperationQueue alloc]init]];網絡
//調用start發送網絡請求 [connect start]; */ //使用自動發送網絡請求模式,發送請求失敗(須要改造代碼) //WHY? /*01 網絡請求發送和數據接收是否成功,和一些因素相關,好比客戶端的網速、服務器端的查詢速度等等。 02 而在子線程中建立的NSURLConnection對象是一個臨時變量,當請求發送完成以後就被釋放了,因此這個時候它的代理方法不會調用用。 03 爲何使用非自動發送網絡請求模式是OK的。 由於在該模式中,調用了start來開始發送網絡請求,該方法內部會自動將當前的connect做爲一個Source添加到當前線程所在的Runloop中 若是當前線程是子線程(即當前線程的runloop並未建立),那麼該方法內部會默認先建立當前線程的Runloop,設置在runloop的默認模式下運行。 此時runloop會對這個Connect對象進行強引用,保證了代理方法被調用的前提 */ NSURLConnection *connect = [[NSURLConnection alloc]initWithRequest:request delegate:self]; [connect setDelegateQueue:[[NSOperationQueue alloc]init]]; //建立當前線程的runloop,並開啓runloop [[NSRunLoop currentRunLoop] run]; }); ```
(1)使用步驟session
使用NSURLSession建立task,而後執行task
(2)關於taskapp
a.NSURLSessionTask是一個抽象類,自己不能使用,只能使用它的子類 b.NSURLSessionDataTask\NSURLSessionUploadTask\NSURLSessionDownloadTask
(3)發送get請求框架
//1.建立NSURLSession對象(能夠獲取單例對象) NSURLSession *session = [NSURLSession sharedSession]; //2.根據NSURLSession對象建立一個Task NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/login?username=ss&pwd=ss&type=JSON"]; NSURLRequest *request = [NSURLRequest requestWithURL:url]; //方法參數說明 /* 注意:該block是在子線程中調用的,若是拿到數據以後要作一些UI刷新操做,那麼須要回到主線程刷新 第一個參數:須要發送的請求對象 block:當請求結束拿到服務器響應的數據時調用block block-NSData:該請求的響應體 block-NSURLResponse:存放本次請求的響應信息,響應頭,真實類型爲NSHTTPURLResponse block-NSErroe:請求錯誤信息 */ NSURLSessionDataTask * dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData * __nullable data, NSURLResponse * __nullable response, NSError * __nullable error) { //拿到響應頭信息 NSHTTPURLResponse *res = (NSHTTPURLResponse *)response; //4.解析拿到的響應數據 NSLog(@"%@\n%@",[[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding],res.allHeaderFields); }]; //3.執行Task //注意:剛建立出來的task默認是掛起狀態的,須要調用該方法來啓動任務(執行任務) [dataTask resume];
(4)發送get請求的第二種方式async
//注意:該方法內部默認會把URL對象包裝成一個NSURLRequest對象(默認是GET請求) //方法參數說明 /* //第一個參數:發送請求的URL地址 //block:當請求結束拿到服務器響應的數據時調用block //block-NSData:該請求的響應體 //block-NSURLResponse:存放本次請求的響應信息,響應頭,真實類型爲NSHTTPURLResponse //block-NSErroe:請求錯誤信息 */ - (nullable NSURLSessionDataTask *)dataTaskWithURL:(NSURL *)url completionHandler:(void (^)(NSData * __nullable data, NSURLResponse * __nullable response, NSError * __nullable error))completionHandler;
(5)發送POST請求ide
//1.建立NSURLSession對象(能夠獲取單例對象) NSURLSession *session = [NSURLSession sharedSession]; //2.根據NSURLSession對象建立一個Task NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/login"]; //建立一個請求對象,並這是請求方法爲POST,把參數放在請求體中傳遞 NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; request.HTTPMethod = @"POST"; request.HTTPBody = [@"username=123&pwd=456&type=JSON" dataUsingEncoding:NSUTF8StringEncoding]; NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData * __nullable data, NSURLResponse * __nullable response, NSError * __nullable error) { //拿到響應頭信息 NSHTTPURLResponse *res = (NSHTTPURLResponse *)response; //解析拿到的響應數據 NSLog(@"%@\n%@",[[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding],res.allHeaderFields); }]; //3.執行Task //注意:剛建立出來的task默認是掛起狀態的,須要調用該方法來啓動任務(執行任務) [dataTask resume];
(1)建立NSURLSession對象,設置代理(默認配置)oop
//1.建立NSURLSession,並設置代理 /* 第一個參數:session對象的全局配置設置,通常使用默認配置就能夠 第二個參數:誰成爲session對象的代理 第三個參數:代理方法在哪一個隊列中執行(在哪一個線程中調用),若是是主隊列那麼在主線程中執行,若是是非主隊列,那麼在子線程中執行 */ NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]];
(2)根據Session對象建立一個NSURLSessionDataTask任務(post和get選擇)
//建立task NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/resources/images/minion_01.png"]; //注意:若是要發送POST請求,那麼請使用dataTaskWithRequest,設置一些請求頭信息 NSURLSessionDataTask *dataTask = [session dataTaskWithURL:url];
(3)執行任務(其它方法,如暫停、取消等)
//啓動task //[dataTask resume]; //其它方法,如取消任務,暫停任務等 //[dataTask cancel]; //[dataTask suspend];
(4)遵照代理協議,實現代理方法(3個相關的代理方法)
/* 1.當接收到服務器響應的時候調用 session:發送請求的session對象 dataTask:根據NSURLSession建立的task任務 response:服務器響應信息(響應頭) completionHandler:經過該block回調,告訴服務器端是否接收返回的數據 */ -(void)URLSession:(nonnull NSURLSession *)session dataTask:(nonnull NSURLSessionDataTask *)dataTask didReceiveResponse:(nonnull NSURLResponse *)response completionHandler:(nonnull void (^)(NSURLSessionResponseDisposition))completionHandler /* 2.當接收到服務器返回的數據時調用 該方法可能會被調用屢次 */ -(void)URLSession:(nonnull NSURLSession *)session dataTask:(nonnull NSURLSessionDataTask *)dataTask didReceiveData:(nonnull NSData *)data /* 3.當請求完成以後調用該方法 不管是請求成功仍是請求失敗都調用該方法,若是請求失敗,那麼error對象有值,不然那麼error對象爲空 */ -(void)URLSession:(nonnull NSURLSession *)session task:(nonnull NSURLSessionTask *)task didCompleteWithError:(nullable NSError *)error
(5)當接收到服務器響應的時候,告訴服務器接收數據(調用block)
//默認狀況下,當接收到服務器響應以後,服務器認爲客戶端不須要接收數據,因此後面的代理方法不會調用 //若是須要繼續接收服務器返回的數據,那麼須要調用block,並傳入對應的策略 /* NSURLSessionResponseCancel = 0, 取消任務 NSURLSessionResponseAllow = 1, 接收任務 NSURLSessionResponseBecomeDownload = 2, 轉變成下載 NSURLSessionResponseBecomeStream NS_ENUM_AVAILABLE(10_11, 9_0) = 3, 轉變成流 */ completionHandler(NSURLSessionResponseAllow);
(1)使用NSURLSession和NSURLSessionDownload能夠很方便的實現文件下載操做
/* 第一個參數:要下載文件的url路徑 第二個參數:當接收完服務器返回的數據以後調用該block location:下載的文件的保存地址(默認是存儲在沙盒中tmp文件夾下面,隨時會被刪除) response:服務器響應信息,響應頭 error:該請求的錯誤信息 */ //說明:downloadTaskWithURL方法已經實現了在下載文件數據的過程當中邊下載文件數據,邊寫入到沙盒文件的操做 NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithURL:url completionHandler:^(NSURL * __nullable location, NSURLResponse * __nullable response, NSError * __nullable error)
(2)downloadTaskWithURL內部默認已經實現了變下載邊寫入操做,因此不用開發人員擔憂內存問題
(3)文件下載後默認保存在tmp文件目錄,須要開發人員手動的剪切到合適的沙盒目錄
(4)缺點:沒有辦法監控下載進度
(1)建立NSURLSession並設置代理,經過NSURLSessionDownloadTask並以代理的方式來完成大文件的下載
//1.建立NSULRSession,設置代理 self.session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]]; //2.建立task NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/resources/videos/minion_01.mp4"]; self.downloadTask = [self.session downloadTaskWithURL:url]; //3.執行task [self.downloadTask resume];
(2)經常使用代理方法的說明
/* 1.當接收到下載數據的時候調用,能夠在該方法中監聽文件下載的進度 該方法會被調用屢次 totalBytesWritten:已經寫入到文件中的數據大小 totalBytesExpectedToWrite:目前文件的總大小 bytesWritten:本次下載的文件數據大小 */ -(void)URLSession:(nonnull NSURLSession *)session downloadTask:(nonnull NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite /* 2.恢復下載的時候調用該方法 fileOffset:恢復以後,要從文件的什麼地方開發下載 expectedTotalBytes:該文件數據的總大小 */ -(void)URLSession:(nonnull NSURLSession *)session downloadTask:(nonnull NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes /* 3.下載完成以後調用該方法 */ -(void)URLSession:(nonnull NSURLSession *)session downloadTask:(nonnull NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(nonnull NSURL *)location /* 4.請求完成以後調用 若是請求失敗,那麼error有值 */ -(void)URLSession:(nonnull NSURLSession *)session task:(nonnull NSURLSessionTask *)task didCompleteWithError:(nullable NSError *)error
(3)實現斷點下載相關代碼
//若是任務,取消了那麼之後就不能恢復了 // [self.downloadTask cancel]; //若是採起這種方式來取消任務,那麼該方法會經過resumeData保存當前文件的下載信息 //只要有了這份信息,之後就能夠經過這些信息來恢復下載 [self.downloadTask cancelByProducingResumeData:^(NSData * __nullable resumeData) { self.resumeData = resumeData; }]; ----------- //繼續下載 //首先經過以前保存的resumeData信息,建立一個下載任務 self.downloadTask = [self.session downloadTaskWithResumeData:self.resumeData]; [self.downloadTask resume];
(4)計算當前下載進度
//獲取文件下載進度 self.progress.progress = 1.0 * totalBytesWritten/totalBytesExpectedToWrite;
(5)侷限性
01 若是用戶點擊暫停以後退出程序,那麼須要把恢復下載的數據寫一份到沙盒,代碼複雜度更 02 若是用戶在下載中途未保存恢復下載數據即退出程序,則不具有可操做性
(1)關於NSOutputStream的使用
//1. 建立一個輸入流,數據追加到文件的屁股上 //把數據寫入到指定的文件地址,若是當前文件不存在,則會自動建立 NSOutputStream *stream = [[NSOutputStream alloc]initWithURL:[NSURL fileURLWithPath:[self fullPath]] append:YES]; //2. 打開流 [stream open]; //3. 寫入流數據 [stream write:data.bytes maxLength:data.length]; //4.當不須要的時候應該關閉流 [stream close];
(2)關於網絡請求請求頭的設置(能夠設置請求下載文件的某一部分)
//1. 設置請求對象 //1.1 建立請求路徑 NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/resources/videos/minion_01.mp4"]; //1.2 建立可變請求對象 NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; //1.3 拿到當前文件的殘留數據大小 self.currentContentLength = [self FileSize]; //1.4 告訴服務器從哪一個地方開始下載文件數據 NSString *range = [NSString stringWithFormat:@"bytes=%zd-",self.currentContentLength]; NSLog(@"%@",range); //1.5 設置請求頭 [request setValue:range forHTTPHeaderField:@"Range"];
(3)NSURLSession對象的釋放
-(void)dealloc { //在最後的時候應該把session釋放,以避免形成內存泄露 // NSURLSession設置過代理後,須要在最後(好比控制器銷燬的時候)調用session的invalidateAndCancel或者resetWithCompletionHandler,纔不會有內存泄露 // [self.session invalidateAndCancel]; [self.session resetWithCompletionHandler:^{ NSLog(@"釋放---"); }]; }
(4)優化部分
01 關於文件下載進度的實時更新 02 方法的獨立與抽取
(1)實現文件上傳的方法
/* 第一個參數:請求對象 第二個參數:請求體(要上傳的文件數據) block回調: NSData:響應體 NSURLResponse:響應頭 NSError:請求的錯誤信息 */ NSURLSessionUploadTask *uploadTask = [session uploadTaskWithRequest:request fromData:data completionHandler:^(NSData * __nullable data, NSURLResponse * __nullable response, NSError * __nullable error)
(2)設置代理,在代理方法中監聽文件上傳進度
/* 調用該方法上傳文件數據 若是文件數據很大,那麼該方法會被調用屢次 參數說明: totalBytesSent:已經上傳的文件數據的大小 totalBytesExpectedToSend:文件的總大小 */ -(void)URLSession:(nonnull NSURLSession *)session task:(nonnull NSURLSessionTask *)task didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesSent totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend { NSLog(@"%.2f",1.0 * totalBytesSent/totalBytesExpectedToSend); }
(3)關於NSURLSessionConfiguration相關
01 做用:能夠統一配置NSURLSession,如請求超時等 02 建立的方式和使用
//建立配置的三種方式 + (NSURLSessionConfiguration *)defaultSessionConfiguration; + (NSURLSessionConfiguration *)ephemeralSessionConfiguration; + (NSURLSessionConfiguration *)backgroundSessionConfigurationWithIdentifier:(NSString *)identifier NS_AVAILABLE(10_10, 8_0); //統一配置NSURLSession -(NSURLSession *)session { if (_session == nil) { //建立NSURLSessionConfiguration NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration]; //設置請求超時爲10秒鐘 config.timeoutIntervalForRequest = 10; //在蜂窩網絡狀況下是否繼續請求(上傳或下載) config.allowsCellularAccess = NO; _session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:[NSOperationQueue mainQueue]]; } return _session; }
AFN結構體 - NSURLConnection + AFURLConnectionOperation + AFHTTPRequestOperation + AFHTTPRequestOperationManager(封裝了經常使用的 HTTP 方法) * 屬性 * baseURL :AFN建議開發者針對 AFHTTPRequestOperationManager 自定義個一個單例子類,設置 baseURL, 全部的網絡訪問,都只使用相對路徑便可 * requestSerializer :請求數據格式/默認是二進制的 HTTP * responseSerializer :響應的數據格式/默認是 JSON 格式 * operationQueue * reachabilityManager :網絡鏈接管理器 * 方法 * manager :方便建立管理器的類方法 * HTTPRequestOperationWithRequest :在訪問服務器時,若是要告訴服務器一些附加信息,都須要在 Request 中設置 * GET * POST - NSURLSession + AFURLSessionManager + AFHTTPSessionManager(封裝了經常使用的 HTTP 方法) * GET * POST * UIKit + AFNetworking 分類 * NSProgress :利用KVO - 半自動的序列化&反序列化的功能 + AFURLRequestSerialization :請求的數據格式/默認是二進制的 + AFURLResponseSerialization :響應的數據格式/默認是JSON格式 - 附加功能 + 安全策略 * HTTPS * AFSecurityPolicy + 網絡檢測 * 對蘋果的網絡鏈接檢測作了一個封裝 * AFNetworkReachabilityManager 建議: 能夠學習下AFN對 UIKit 作了一些分類, 對本身能力提高是很是有幫助的
(1)發送GET請求的兩種方式(POST同)
-(void)get1 { //1.建立AFHTTPRequestOperationManager管理者 //AFHTTPRequestOperationManager內部是基於NSURLConnection實現的 AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager]; //2.發送請求 /* http://120.25.226.186:32812/login?username=ee&pwd=ee&type=JSON 第一個參數:NSString類型的請求路徑,AFN內部會自動將該路徑包裝爲一個url並建立請求對象 第二個參數:請求參數,以字典的方式傳遞,AFN內部會判斷當前是POST請求仍是GET請求,以選擇直接拼接仍是轉換爲NSData放到請求體中傳遞 第三個參數:請求成功以後回調Block 第四個參數:請求失敗回調Block */ NSDictionary *param = @{ @"username":@"123", @"pwd":@"456" }; //注意:字符串中不能包含空格 [manager GET:@"http://120.25.226.186:32812/login" parameters:param success:^(AFHTTPRequestOperation * _Nonnull operation, id _Nonnull responseObject) { NSLog(@"請求成功---%@",responseObject); } failure:^(AFHTTPRequestOperation * _Nonnull operation, NSError * _Nonnull error) { NSLog(@"失敗---%@",error); }]; } -(void)get2 { //1.建立AFHTTPSessionManager管理者 //AFHTTPSessionManager內部是基於NSURLSession實現的 AFHTTPSessionManager *manager = [AFHTTPSessionManager manager]; //2.發送請求 NSDictionary *param = @{ @"username":@"123", @"pwd":@"456" }; //注意:responseObject:請求成功返回的響應結果(AFN內部已經把響應體轉換爲OC對象,一般是字典或數組) [manager GET:@"http://120.25.226.186:32812/login" parameters:param success:^(NSURLSessionDataTask * _Nonnull task, id _Nonnull responseObject) { NSLog(@"請求成功---%@",[responseObject class]); } failure:^(NSURLSessionDataTask * _Nonnull task, NSError * _Nonnull error) { NSLog(@"失敗---%@",error); }]; }
(2)使用AFN下載文件
-(void)download { //1.建立一個管理者 AFHTTPSessionManager *manage = [AFHTTPSessionManager manager]; //2.下載文件 /* 第一個參數:請求對象 第二個參數:下載進度 第三個參數:block回調,須要返回一個url地址,用來告訴AFN下載文件的目標地址 targetPath:AFN內部下載文件存儲的地址,tmp文件夾下 response:請求的響應頭 返回值:文件應該剪切到什麼地方 第四個參數:block回調,當文件下載完成以後調用 response:響應頭 filePath:文件存儲在沙盒的地址 == 第三個參數中block的返回值 error:錯誤信息 */ //2.1 建立請求對象 NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://120.25.226.186:32812/resources/images/minion_02.png"]]; //2.2 建立下載進度,並監聽 NSProgress *progress = nil; NSURLSessionDownloadTask *downloadTask = [manage downloadTaskWithRequest:request progress:&progress destination:^NSURL * _Nonnull(NSURL * _Nonnull targetPath, NSURLResponse * _Nonnull response) { NSString *caches = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject]; //拼接文件全路徑 NSString *fullpath = [caches stringByAppendingPathComponent:response.suggestedFilename]; NSURL *filePathUrl = [NSURL fileURLWithPath:fullpath]; return filePathUrl; } completionHandler:^(NSURLResponse * _Nonnull response, NSURL * _Nonnull filePath, NSError * _Nonnull error) { NSLog(@"文件下載完畢---%@",filePath); }]; //2.3 使用KVO監聽下載進度 [progress addObserver:self forKeyPath:@"completedUnitCount" options:NSKeyValueObservingOptionNew context:nil]; //3.啓動任務 [downloadTask resume]; } //獲取並計算當前文件的下載進度 -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(NSProgress *)progress change:(NSDictionary<NSString *,id> *)change context:(void *)context { NSLog(@"%zd--%zd--%f",progress.completedUnitCount,progress.totalUnitCount,1.0 * progress.completedUnitCount/progress.totalUnitCount); }