目錄
1. AFN框架
2. NSURLSession
3. NSURLConnection
4. 其他
Cocoa 中網絡編程層次結構分為三層,自上而下分別是:
Cocoa 層:NSURL,Bonjour,Game Kit,WebKit
Core Foundation 層:基于 C 的 CFNetwork 和 CFNetServices
OS 層:基于 C 的 BSD socket
1. AFN框架(第三方庫)常用
// 1.創建網絡請求manager
AFHTTPSessionManager *manger=[AFHTTPSessionManager manager];
// 1.1 設置請求的數據類型
// 設置 request類型為二進制類型(默認)
[manger setRequestSerializer:[AFHTTPRequestSerializer serializer]];
// 設置 請求超時時間
[manger.requestSerializer setTimeoutInterval:6.f];
// 1.2 設置 response
// 設置 response類型為二進制類型(默認:JSON類型,已經解析)
[manger setResponseSerializer:[AFHTTPResponseSerializer serializer]];
// 設置 允許接收的數據類型
[manger.responseSerializer setAcceptableContentTypes:[NSSet setWithObjects:@"application/json",@"text/json", @"text/javascript",@"text/html", nil]];
// 2.發送請求
// url
NSString *urlStr=@"url";
// 參數(可以是數組/字典/nil)
NSDictionary *paraDic=@{@"userId":@""};
[manger POST:urlStr parameters:paraDic progress:^(NSProgress * _Nonnull uploadProgress) {
} success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
// 主線程:可以直接更新UI
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
NSLog(@"%@發生錯誤: \n%@",urlStr,error);
}];
'
請求格式
AFHTTPRequestSerializer 二進制格式 (默認)
AFJSONRequestSerializer JSON
AFPropertyListRequestSerializer PList(是一種特殊的XML,解析起來相對容易)
返回格式
AFHTTPResponseSerializer 二進制格式 (不作任何處理:NSData,當返回的數據不是JSON/XML/plist/image要設置,如:HTML、Text)
AFJSONResponseSerializer JSON (默認)
AFXMLParserResponseSerializer XML,只能返回XMLParser,還需要自己通過代理方法解析
AFXMLDocumentResponseSerializer (Mac OS X)
AFPropertyListResponseSerializer PList
AFImageResponseSerializer Image
AFCompoundResponseSerializer 組合
'
上傳
// url
NSString *urlStr=@"url";
// 參數(可以是數組/字典/nil)
NSDictionary *paraDic=@{@"userId":@""};
[manger POST:urlStr parameters:paraDic constructingBodyWithBlock:^(id<AFMultipartFormData> _Nonnull formData) {
// img->data
NSData *imgData=UIImagePNGRepresentation([UIImage imageNamed:@""]);
// 設置需要上傳的文件(需要上傳的文件,后臺規定的參數名,文件名,后臺規定的文件類型)
[formData appendPartWithFileData:imgData name:@"headImage" fileName:@"hello.png" mimeType:@"image/png"];
} progress:^(NSProgress * _Nonnull uploadProgress) {
//
} success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
// 上傳成功
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
NSLog(@"%@發生錯誤: \n%@",urlStr,error);
}];
2. NSURLSession (原生網絡請求類---目前用)
// 1.創建請求
NSURLRequest *request=[NSURLRequest requestWithURL:[NSURL URLWithString:@"urlStr"]];
// 2.創建會話
NSURLSession *session=[NSURLSession sharedSession];
// 3.創建任務
NSURLSessionDataTask *task=[session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if(error==nil){
NSDictionary *dcit=[NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:nil];
// 刷新UI在主線程中
}
}];
// 3.1啟動任務
[task resume];
繼承關系:
NSObject
NSURLSessionTask
NSURLSessionDataTask NSURLSessionDownloadTask
NSURLSessionUploadTask
說明:
NSURLSessionUploadTask 上傳專用Task(不接收數據)
NSURLSessionDownloadTask 下載專用Task
NSURLSessionDataTask 上傳數據,并接收返回數據
0.創建NSURLRequest (3方式)
方式一
NSURLRequest *request=[NSURLRequest requestWithURL:[NSURL URLWithString:@""]];
方式二
NSURLRequest *request=[NSURLRequest requestWithURL:[NSURL URLWithString:@""] cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:2];
方式三
NSMutableURLRequest *muRequest=[NSMutableURLRequest requestWithURL:[NSURL URLWithString:@""]];
// 設置請求超時時間
[muRequest setTimeoutInterval:10];
// 默認GET,設置請求方式
[muRequest setHTTPMethod:@"POST"];
// 設置請求體
[muRequest setHTTPBody:[@"key=value&key2=value2" dataUsingEncoding:NSUTF8StringEncoding]];
// 設置請求頭
[muRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
// [muRequest addValue:@"" forHTTPHeaderField:@"Content-Length"];
1. 創建NSURLSession(3方式)
方式一 全局Session(有局限)
NSURLSession *session=[NSURLSession sharedSession];
方式二 SessionConfiguration
NSURLSessionConfiguration *connfi=[NSURLSessionConfiguration defaultSessionConfiguration];
[connfi setTimeoutIntervalForRequest:5]; // 設置請求超時
NSURLSession *session=[NSURLSession sessionWithConfiguration:connfi];
NSURLSession *session=[NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:[NSOperationQueue mainQueue]];
后臺Session
NSURLSessionConfiguration *config=[NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:@""];
NSURLSession *session=[NSURLSession sessionWithConfiguration:connfi];
2. 創建NSURLSessionConfiguration (3方式)
方式一
// 存儲Cache在硬盤(默認模式,保存用戶的證書到鑰匙串,使用共享cookie存儲)
NSURLSessionConfiguration *config=[NSURLSessionConfiguration defaultSessionConfiguration];
方式二
// 存儲Cache在內存(用于無痕瀏覽,會話結束后清空數據)
NSURLSessionConfiguration *config=[NSURLSessionConfiguration ephemeralSessionConfiguration];
方式三
// 將上傳下載移到后臺
NSURLSessionConfiguration *config=[NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:@""];
3. 創建NSURLSessionDataTask (4方式)
方式一
NSURLSessionDataTask *dataTask=[session dataTaskWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@""]]];
方式二
NSURLSessionDataTask *dataTask=[session dataTaskWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@""]] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if(!error){
}
}];
方式三
NSURLSessionDataTask *dataTask=[session dataTaskWithURL:[NSURL URLWithString:@""]];
方式四
NSURLSessionDataTask *dataTask=[session dataTaskWithURL:[NSURL URLWithString:@""] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if(!error){
}
}];
[dataTask resume]; // 任務開始
[dataTask suspend]; // 任務暫停
[dataTask cancel]; // 任務取消
4. 創建NSURLSessionUploadTask(5方式) 上傳data
方式一
[session uploadTaskWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@""]] fromData:data];
方式二
[session uploadTaskWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@""]] fromData:data completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if(!error){
}
}];
方式三
[session uploadTaskWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@""]] fromFile:[[NSURL alloc]initFileURLWithPath:[NSString stringWithFormat:@"%@/Documents/1.txt",NSHomeDirectory()]]];
方式四
[session uploadTaskWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@""]] fromFile:[[NSURL alloc]initFileURLWithPath:[NSString stringWithFormat:@"%@/Documents/1.txt",NSHomeDirectory()]] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if(!error){
}
}];
方式五
[session uploadTaskWithStreamedRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@""]]];
2. 創建NSURLSessionDownloadTask(6方式)下載data
方式一
NSURLSessionDownloadTask *dataTask=[session downloadTaskWithURL:[NSURL URLWithString:@""]];
方式二
NSURLSessionDownloadTask *dataTask=[session downloadTaskWithURL:[NSURL URLWithString:@""] completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if(!error){
}
}];
方式三
NSURLSessionDownloadTask *dataTask=[session downloadTaskWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@""]]];
方式四
NSURLSessionDownloadTask *dataTask=[session downloadTaskWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@""]] completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if(!error){
}
}];
方式五
NSURLSessionDownloadTask *dataTask=[session downloadTaskWithResumeData:data];
方式六
NSURLSessionDownloadTask *dataTask=[session downloadTaskWithResumeData:data completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if(!error){
}
}];
3. NSURLConnection(原生網絡請求類---已過時)
方式一:使用dele(異步)
<NSURLConnectionDelegate,NSURLConnectionDataDelegate>
@property (nonatomic,strong) NSMutableData *contentData;
// 1.創建請求,建立連接
NSURLRequest *request=[NSURLRequest requestWithURL:[NSURL URLWithString:@"urlStr"]];
NSURLConnection *conn=[NSURLConnection connectionWithRequest:request delegate:self];
[conn start];
#pragma mark dele
// 2.收到響應時調用
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
_contentData.length=0;
}
// 3.收到數據時調用
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
[_contentData appendData:data];
}
// 4.數據接收完畢后調用
-(void)connectionDidFinishLoading:(NSURLConnection *)connection{}
// 4.1連接出錯時調用
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{}
方式二:sendAsynchronousRequest(異步)
[NSURLConnection sendAsynchronousRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"urlStr"]] queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse * _Nullable response, NSData * _Nullable data, NSError * _Nullable connectionError) {
}];
方式三:sendSynchronousRequest(同步)
NSData *contentData=[NSURLConnection sendSynchronousRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"urlStr"]] returningResponse:&response error:nil];
上面提到的 NSURLConnection 的異步方法實際上還是跑在主線程當中,在主線程中執行網絡操作會帶來兩個問題:
盡管在網絡連接過程中不會對主線程造成阻塞,但是 delegate 的回調方法還是在主線程中執行的。如果我們在回調方法中(特別是 completion 回調)中進行了大量的耗時操作,仍然會造成主線程的阻塞。
NSURLConnection 默認會跑在當前的 runloop 中,并且跑在 Default Mode,當用戶執行滾動的 UI 操作時會發生 runloop mode 的切換,也就導致了 NSURLConnection 不能及時執行和完成回調。
簡單地把start函數放到后臺的 queue 中是不行的。因為 dispatch_async 開出的線程中,默認 runloop 沒有執行,因此線程會立即結束,來不及調用回調方法。
dispatch_async(connectionQueue, ^{
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
[request setURL:[NSURL URLWithString:[NSString stringWithFormat:someURL]]];
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self]; // 沒有設置 startImmediately 為 NO,會立即開始
//[connection start]; 這一句沒有必要寫,寫了也一樣不能 work。
});
這樣又帶來一個問題,這個線程中 runloop 會一直跑著,導致這個線程也一直不結束
dispatch_async(connectionQueue, ^{
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
[request setURL:[NSURL URLWithString:[NSString stringWithFormat:someURL]]];
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
[[NSRunLoop currentRunLoop] run];
});
dispatch_async(connectionQueue, ^{
NSRunLoop* runLoop = [NSRunLoop currentRunLoop];
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode]; // 添加 inputSource,讓 runloop 保持 alive
[self.connection scheduleInRunLoop:runLoop
forMode:NSDefaultRunLoopMode];
[self.connection start];
[runLoop run];
});
- (void)connectionDidFinishLoading:(NSURLConnection *)connection{
CFRunLoopStop(CFRunLoopGetCurrent());
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{
CFRunLoopStop(CFRunLoopGetCurrent());
}
方法二(使用NSOperationQueue)
dispatch_async(connectionQueue, ^{
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:aURLRequest
delegate:self
startImmediately:NO];
[connection setDelegateQueue:[[NSOperationQueue alloc] init]];
[connection start];
});
4. 其他
注意:
1.App如果需要進行網絡操作,則要在info.plist文件中添加權限:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
菊花
// 是否打開菊花(狀態欄上:用來提示用戶正在請求網絡)
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:true];
data<->str
// data->str
NSString *str=[[NSString alloc]initWithData:[NSData new] encoding:NSUTF8StringEncoding];
// str->data
NSData *data=[@"" dataUsingEncoding:NSUTF8StringEncoding];
中文
對中文編碼(url中有中文)
string.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLFragmentAllowedCharacterSet())!
對中文解碼
paraStr.stringByRemovingPercentEncoding
Charles(抓包)
1. 下載charles
2. 連上同一Wifi,手機設置wifi為手動:服務器(mac終端:ifconfig en0 找到地址) 端口:8888(默認:charles的Proxy中設置的)
3. OK
XML(過時, 現只用于存儲,不用于傳輸)
用來存儲和傳遞數據(優點:可讀性強,缺點:太冗余)
文檔由節點(開始標簽和結束標簽組成)構成
DOM解析:
一次性將整個XML文檔加載進內存,比較適合解析小文件,例如:GDataXml解析
SAX解析:
從根元素開始,按順序一個元素一個元素往下解析,比較適合解析大文件,例如:NSXMLParser解析
例:
<?xml version="1.0" encoding="UTF-8">
<books> 根節點
<book id="0"> id是屬性節點
<name>book1</name> name是元素節點
<price>12</price>
</book>
</books>
使用(2方式):
—————————方式一
第一步:
1. 導入Data文件,建立橋接文件 : #import "GDataXMLNode.h"
2. Build Phase | Link Binary With Libraries 添加libxml2.tbd
3. Build Phase | Compile Source .m文件后雙擊 + -fno-objc-arc
4. BuildSettings | header Search Path + /usr/include/libxml2
第二步:
let path=NSBundle.mainBundle().pathForResource("xml", ofType: "txt")
let data=NSData.init(contentsOfFile: path!)
let doc=try! GDataXMLDocument.init(data: data, options: 0) // 解析數據
let rootE=doc.rootElement() // 獲取根節點
print(rootE.XMLString()) // 打印節點
// 獲取節點的內容(根據節點名)
let booksArr=rootE.elementsForName("books") as! [GDataXMLElement] // 獲取元素節點(返回數組)
let booksEl=booksArr[0]
let bookArr=booksEl.elementsForName("book") as! [GDataXMLElement]
for bookEl in bookArr{
let name=(bookEl.elementsForName("name")[0]) as! GDataXMLElement //
let attName=bookEl.attributes()[0] as! GDataXMLNode // 獲取屬性節點(返回數組)
print(name.stringValue()) // 節點值 name.name()節點名
}
—————————方式二
XPath 使用路徑表達式獲取節點
/ 從根節點獲取
// 查詢和名稱相同的節點(不考慮位置)
. 獲取當前節點
.. 獲取當前節點的父節點
@ 獲取屬性
// 位置
let pathT="/root/books/book/name"
// 查詢所有 符合pathT位置 的節點
let nameArr=try! doc.nodesForXPath(pathT)
print(nameArr[0].stringValue())
擴展:
根名 獲取 根名 元素的所有子節點
/根名 獲取 根名 元素的所有子節點
/根名/元素名 獲取 和路徑匹配的所有元素節點
//元素名 獲取 元素名相同的所有元素節點
根名/元素名//元素名 獲取 元素名相同的所有元素節點(在根名/元素名 下)
//@屬性名 獲取 屬性名相同 的所有元素節點
/根名/元素名[1] 獲取第一個滿足path的元素節點
[last()] 最后一個
[position()<3] 前2個
[屬性名>3]/元素名
//title[@length] 或取所有 屬性名length的元素名title 的元素
//title[@length='e'] 或取所有 屬性名length且值為e的元素名title 的元素
* 匹配任何元素節點。
@* 匹配任何屬性節點。
node() 匹配任何類型的節點。
| 多個路徑
監測網絡狀態 (需引入AFN框架)
1. cocoaPods
pod 'AFNetworking'
2.AppDelegate+
#import <AFNetworking/AFNetworking.h>
// 監聽網絡狀態
-(void)mangeNet{
// 1.獲取網絡管理者
AFNetworkReachabilityManager *netManger=[AFNetworkReachabilityManager sharedManager];
// 2.網絡狀態發生變化后調用
[netManger setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
switch (status) {
case AFNetworkReachabilityStatusUnknown:{
// 檢測到網絡狀態前為此狀態
NSLog(@"網絡未知");
}
break;
case AFNetworkReachabilityStatusNotReachable:{
NSLog(@"連接不到網絡");
// 提示用戶,跳到系統設置頁設置網絡
}
break;
case AFNetworkReachabilityStatusReachableViaWWAN:{
NSLog(@"流量");
}
break;
case AFNetworkReachabilityStatusReachableViaWiFi:{
NSLog(@"wifi");
}
break;
}
}];
// 3.監測網絡變化
[netManger startMonitoring];
}
// 獲取當前網絡狀態
AFNetworkReachabilityStatus status=netManger.networkReachabilityStatus;
// 跳轉到設置---蜂窩網絡
[[UIApplication sharedApplication]openURL:[NSURL URLWithString:@"App-Prefs:root=MOBILE_DATA_SETTINGS_ID"]];
@interface AFHTTPSessionManager : AFURLSessionManager <NSSecureCoding, NSCopying>
// 獲取相對路徑(readonly)
/**
相對路徑的使用
NSURL *baseURL = [NSURL URLWithString:@"http://example.com/v1/"];
[NSURL URLWithString:@"foo" relativeToURL:baseURL]; // http://example.com/v1/foo
[NSURL URLWithString:@"foo?bar=baz" relativeToURL:baseURL]; // http://example.com/v1/foo?bar=baz
[NSURL URLWithString:@"/foo" relativeToURL:baseURL]; // http://example.com/foo
[NSURL URLWithString:@"foo/" relativeToURL:baseURL]; // http://example.com/v1/foo
[NSURL URLWithString:@"/foo/" relativeToURL:baseURL]; // http://example.com/foo/
[NSURL URLWithString:@"http://example2.com/" relativeToURL:baseURL]; // http://example2.com/
*/
@property (readonly, nonatomic, strong, nullable) NSURL *baseURL;
/**
以什么格式序列化請求體,默認:AFHTTPRequestSerializer
requestSerializer不能為nil
*/
@property (nonatomic, strong) AFHTTPRequestSerializer <AFURLRequestSerialization> * requestSerializer;
/**
以什么格式序列化響應體,默認:AFJSONResponseSerializer
responseSerializer不能為nil
*/
@property (nonatomic, strong) AFHTTPResponseSerializer <AFURLResponseSerialization> * responseSerializer;
///-------------------------------
/// @name Managing Security Policy
///-------------------------------
/**
The security policy used by created session to evaluate server trust for secure connections. `AFURLSessionManager` uses the `defaultPolicy` unless otherwise specified. A security policy configured with `AFSSLPinningModePublicKey` or `AFSSLPinningModeCertificate` can only be applied on a session manager initialized with a secure base URL (i.e. https). Applying a security policy with pinning enabled on an insecure session manager throws an `Invalid Security Policy` exception.
*/
@property (nonatomic, strong) AFSecurityPolicy *securityPolicy;
///---------------------
/// @name Initialization
///---------------------
// 獲取AFHTTPSessionManager對象
+ (instancetype)manager;
/**
Initializes an `AFHTTPSessionManager` object with the specified base URL.
@param url The base URL for the HTTP client.
@return The newly-initialized HTTP client
*/
- (instancetype)initWithBaseURL:(nullable NSURL *)url;
/**
Initializes an `AFHTTPSessionManager` object with the specified base URL.
This is the designated initializer.
@param url The base URL for the HTTP client.
@param configuration The configuration used to create the managed session.
@return The newly-initialized HTTP client
*/
- (instancetype)initWithBaseURL:(nullable NSURL *)url
sessionConfiguration:(nullable NSURLSessionConfiguration *)configuration NS_DESIGNATED_INITIALIZER;
///---------------------------
/// @name Making HTTP Requests
///---------------------------
/**
創建并運行一個NSURLSessionDataTask的GET方式任務請求
內部調用dataTaskWithHTTPMethod方法
URLString:請求字符串
parameters:參數字典(根據requestSerializer序列化)
headers:追加至默認的請求頭
downloadProgress:下載過程中的回調,不在主線程隊列中執行。
success:請求成功并獲得響應時調用
failure:出錯時的回調(請求出錯、解析出錯)
*/
- (nullable NSURLSessionDataTask *)GET:(NSString *)URLString
parameters:(nullable id)parameters
headers:(nullable NSDictionary <NSString *, NSString *> *)headers
progress:(nullable void (^)(NSProgress *downloadProgress))downloadProgress
success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;
/**
Creates and runs an `NSURLSessionDataTask` with a `HEAD` request.
@param URLString The URL string used to create the request URL.
@param parameters The parameters to be encoded according to the client request serializer.
@param headers The headers appended to the default headers for this request.
@param success A block object to be executed when the task finishes successfully. This block has no return value and takes a single arguments: the data task.
@param failure A block object to be executed when the task finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a two arguments: the data task and the error describing the network or parsing error that occurred.
@see -dataTaskWithRequest:completionHandler:
*/
- (nullable NSURLSessionDataTask *)HEAD:(NSString *)URLString
parameters:(nullable id)parameters
headers:(nullable NSDictionary <NSString *, NSString *> *)headers
success:(nullable void (^)(NSURLSessionDataTask *task))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;
/**
創建并運行一個NSURLSessionDataTask的POST方式任務請求
內部調用dataTaskWithHTTPMethod方法
URLString:請求字符串
parameters:參數字典(根據requestSerializer序列化)
headers:追加至默認的請求頭
uploadProgress:上傳過程中的回調,不在主線程隊列中執行。
success:請求成功并獲得響應時調用
failure:出錯時的回調(請求出錯、解析出錯)
*/
- (nullable NSURLSessionDataTask *)POST:(NSString *)URLString
parameters:(nullable id)parameters
headers:(nullable NSDictionary <NSString *, NSString *> *)headers
progress:(nullable void (^)(NSProgress *uploadProgress))uploadProgress
success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;
/**
創建并運行一個NSURLSessionDataTask的POST方式任務請求(用于上傳圖片等)
內部調用uploadTaskWithStreamedRequest方法
URLString:請求字符串
parameters:參數字典(根據requestSerializer序列化)
headers:追加至默認的請求頭
block:追加data至請求體
uploadProgress:上傳過程中的回調,不在主線程隊列中執行。
success:請求成功并獲得響應時調用
failure:出錯時的回調(請求出錯、解析出錯)
*/
- (nullable NSURLSessionDataTask *)POST:(NSString *)URLString
parameters:(nullable id)parameters
headers:(nullable NSDictionary <NSString *, NSString *> *)headers
constructingBodyWithBlock:(nullable void (^)(id <AFMultipartFormData> formData))block
progress:(nullable void (^)(NSProgress *uploadProgress))uploadProgress
success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;
/**
Creates and runs an `NSURLSessionDataTask` with a `PUT` request.
@param URLString The URL string used to create the request URL.
@param parameters The parameters to be encoded according to the client request serializer.
@param headers The headers appended to the default headers for this request.
@param success A block object to be executed when the task finishes successfully. This block has no return value and takes two arguments: the data task, and the response object created by the client response serializer.
@param failure A block object to be executed when the task finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a two arguments: the data task and the error describing the network or parsing error that occurred.
@see -dataTaskWithRequest:completionHandler:
*/
- (nullable NSURLSessionDataTask *)PUT:(NSString *)URLString
parameters:(nullable id)parameters
headers:(nullable NSDictionary <NSString *, NSString *> *)headers
success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;
/**
Creates and runs an `NSURLSessionDataTask` with a `PATCH` request.
@param URLString The URL string used to create the request URL.
@param parameters The parameters to be encoded according to the client request serializer.
@param headers The headers appended to the default headers for this request.
@param success A block object to be executed when the task finishes successfully. This block has no return value and takes two arguments: the data task, and the response object created by the client response serializer.
@param failure A block object to be executed when the task finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a two arguments: the data task and the error describing the network or parsing error that occurred.
@see -dataTaskWithRequest:completionHandler:
*/
- (nullable NSURLSessionDataTask *)PATCH:(NSString *)URLString
parameters:(nullable id)parameters
headers:(nullable NSDictionary <NSString *, NSString *> *)headers
success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;
/**
Creates and runs an `NSURLSessionDataTask` with a `DELETE` request.
@param URLString The URL string used to create the request URL.
@param parameters The parameters to be encoded according to the client request serializer.
@param headers The headers appended to the default headers for this request.
@param success A block object to be executed when the task finishes successfully. This block has no return value and takes two arguments: the data task, and the response object created by the client response serializer.
@param failure A block object to be executed when the task finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a two arguments: the data task and the error describing the network or parsing error that occurred.
@see -dataTaskWithRequest:completionHandler:
*/
- (nullable NSURLSessionDataTask *)DELETE:(NSString *)URLString
parameters:(nullable id)parameters
headers:(nullable NSDictionary <NSString *, NSString *> *)headers
success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;
/**
創建并運行一個NSURLSessionDataTask任務請求
內部調用dataTaskWithHTTPMethod方法
method:POST、GET
URLString:請求字符串
parameters:參數字典(根據requestSerializer序列化)
headers:追加至默認的請求頭
uploadProgress:上傳過程中的回調,不在主線程隊列中執行。
downloadProgress:下載過程中的回調,不在主線程隊列中執行。
success:請求成功并獲得響應時調用
failure:出錯時的回調(請求出錯、解析出錯)
*/
- (nullable NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(nullable id)parameters
headers:(nullable NSDictionary <NSString *, NSString *> *)headers
uploadProgress:(nullable void (^)(NSProgress *uploadProgress))uploadProgress
downloadProgress:(nullable void (^)(NSProgress *downloadProgress))downloadProgress
success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;
@end
//
- (nullable NSURLSessionDataTask *)POST:(NSString *)URLString
parameters:(nullable id)parameters
headers:(nullable NSDictionary <NSString *, NSString *> *)headers
progress:(nullable void (^)(NSProgress *uploadProgress))uploadProgress
success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure
{
// 創建NSURLSessionDataTask并執行
NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"POST" URLString:URLString parameters:parameters headers:headers uploadProgress:uploadProgress downloadProgress:nil success:success failure:failure];
[dataTask resume];
return dataTask;
}
//
- (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(nullable id)parameters
headers:(nullable NSDictionary <NSString *, NSString *> *)headers
uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgress
downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgress
success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure
{
// 創建NSMutableURLRequest請求
NSError *serializationError = nil;
NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];
// 設置head請求頭
for (NSString *headerField in headers.keyEnumerator) {
[request setValue:headers[headerField] forHTTPHeaderField:headerField];
}
// 出錯
if (serializationError) {
if (failure) {
dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{
failure(nil, serializationError);
});
}
return nil;
}
// 創建NSURLSessionDataTask任務,并返回
__block NSURLSessionDataTask *dataTask = nil;
dataTask = [self dataTaskWithRequest:request
uploadProgress:uploadProgress
downloadProgress:downloadProgress
completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *error) {
if (error) {
if (failure) { // failure回調
failure(dataTask, error);
}
} else {
if (success) { // succes回調
success(dataTask, responseObject);
}
}
}];
return dataTask;
}
//
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request
uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock
downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock
completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject, NSError * _Nullable error))completionHandler {
// 創建NSURLSessionDataTask任務
NSURLSessionDataTask *dataTask = [self.session dataTaskWithRequest:request];
[self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler];
return dataTask;
}
//
- (void)addDelegateForDataTask:(NSURLSessionDataTask *)dataTask
uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock
downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock
completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler
{
AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] initWithTask:dataTask];
delegate.manager = self;
delegate.completionHandler = completionHandler;
delegate.uploadProgressBlock = uploadProgressBlock;
delegate.downloadProgressBlock = downloadProgressBlock;
dataTask.taskDescription = self.taskDescriptionForSessionTasks;
[self setDelegate:delegate forTask:dataTask];
}
//
- (void)setDelegate:(AFURLSessionManagerTaskDelegate *)delegate
forTask:(NSURLSessionTask *)task
{
NSParameterAssert(task);
NSParameterAssert(delegate);
[self.lock lock];
self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate;
[self addNotificationObserverForTask:task];
[self.lock unlock];
}
- (void)addNotificationObserverForTask:(NSURLSessionTask *)task {
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(taskDidResume:) name:AFNSURLSessionTaskDidResumeNotification object:task];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(taskDidSuspend:) name:AFNSURLSessionTaskDidSuspendNotification object:task];
}
- (void)taskDidResume:(NSNotification *)notification {
NSURLSessionTask *task = notification.object;
if ([task respondsToSelector:@selector(taskDescription)]) {
if ([task.taskDescription isEqualToString:self.taskDescriptionForSessionTasks]) {
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidResumeNotification object:task];
});
}
}
}
- (void)taskDidSuspend:(NSNotification *)notification {
NSURLSessionTask *task = notification.object;
if ([task respondsToSelector:@selector(taskDescription)]) {
if ([task.taskDescription isEqualToString:self.taskDescriptionForSessionTasks]) {
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidSuspendNotification object:task];
});
}
}
}
AFURLSessionManagerTaskDelegate
@interface AFURLSessionManagerTaskDelegate : NSObject <NSURLSessionTaskDelegate, NSURLSessionDataDelegate, NSURLSessionDownloadDelegate>
- (instancetype)initWithTask:(NSURLSessionTask *)task;
@property (nonatomic, weak) AFURLSessionManager *manager;
@property (nonatomic, strong) NSMutableData *mutableData;
@property (nonatomic, strong) NSProgress *uploadProgress;
@property (nonatomic, strong) NSProgress *downloadProgress;
@property (nonatomic, copy) NSURL *downloadFileURL;
#if AF_CAN_INCLUDE_SESSION_TASK_METRICS
@property (nonatomic, strong) NSURLSessionTaskMetrics *sessionTaskMetrics AF_API_AVAILABLE(ios(10), macosx(10.12), watchos(3), tvos(10));
#endif
@property (nonatomic, copy) AFURLSessionDownloadTaskDidFinishDownloadingBlock downloadTaskDidFinishDownloading;
@property (nonatomic, copy) AFURLSessionTaskProgressBlock uploadProgressBlock;
@property (nonatomic, copy) AFURLSessionTaskProgressBlock downloadProgressBlock;
@property (nonatomic, copy) AFURLSessionTaskCompletionHandler completionHandler;
@end
@implementation AFURLSessionManagerTaskDelegate
- (instancetype)initWithTask:(NSURLSessionTask *)task {
self = [super init];
if (!self) {
return nil;
}
_mutableData = [NSMutableData data];
_uploadProgress = [[NSProgress alloc] initWithParent:nil userInfo:nil];
_downloadProgress = [[NSProgress alloc] initWithParent:nil userInfo:nil];
__weak __typeof__(task) weakTask = task;
for (NSProgress *progress in @[ _uploadProgress, _downloadProgress ])
{
progress.totalUnitCount = NSURLSessionTransferSizeUnknown;
progress.cancellable = YES;
progress.cancellationHandler = ^{
[weakTask cancel];
};
progress.pausable = YES;
progress.pausingHandler = ^{
[weakTask suspend];
};
#if AF_CAN_USE_AT_AVAILABLE
if (@available(macOS 10.11, *))
#else
if ([progress respondsToSelector:@selector(setResumingHandler:)])
#endif
{
progress.resumingHandler = ^{
[weakTask resume];
};
}
[progress addObserver:self
forKeyPath:NSStringFromSelector(@selector(fractionCompleted))
options:NSKeyValueObservingOptionNew
context:NULL];
}
return self;
}
- (void)dealloc {
[self.downloadProgress removeObserver:self forKeyPath:NSStringFromSelector(@selector(fractionCompleted))];
[self.uploadProgress removeObserver:self forKeyPath:NSStringFromSelector(@selector(fractionCompleted))];
}
#pragma mark - NSProgress Tracking
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
if ([object isEqual:self.downloadProgress]) {
if (self.downloadProgressBlock) {
self.downloadProgressBlock(object);
}
}
else if ([object isEqual:self.uploadProgress]) {
if (self.uploadProgressBlock) {
self.uploadProgressBlock(object);
}
}
}
static const void * const AuthenticationChallengeErrorKey = &AuthenticationChallengeErrorKey;
#pragma mark - NSURLSessionTaskDelegate
- (void)URLSession:(__unused NSURLSession *)session
task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
error = objc_getAssociatedObject(task, AuthenticationChallengeErrorKey) ?: error;
__strong AFURLSessionManager *manager = self.manager;
__block id responseObject = nil;
NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
userInfo[AFNetworkingTaskDidCompleteResponseSerializerKey] = manager.responseSerializer;
//Performance Improvement from #2672
NSData *data = nil;
if (self.mutableData) {
data = [self.mutableData copy];
//We no longer need the reference, so nil it out to gain back some memory.
self.mutableData = nil;
}
#if AF_CAN_USE_AT_AVAILABLE && AF_CAN_INCLUDE_SESSION_TASK_METRICS
if (@available(iOS 10, macOS 10.12, watchOS 3, tvOS 10, *)) {
if (self.sessionTaskMetrics) {
userInfo[AFNetworkingTaskDidCompleteSessionTaskMetrics] = self.sessionTaskMetrics;
}
}
#endif
if (self.downloadFileURL) {
userInfo[AFNetworkingTaskDidCompleteAssetPathKey] = self.downloadFileURL;
} else if (data) {
userInfo[AFNetworkingTaskDidCompleteResponseDataKey] = data;
}
if (error) {
userInfo[AFNetworkingTaskDidCompleteErrorKey] = error;
dispatch_group_async(manager.completionGroup ?: url_session_manager_completion_group(), manager.completionQueue ?: dispatch_get_main_queue(), ^{
if (self.completionHandler) {
self.completionHandler(task.response, responseObject, error);
}
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidCompleteNotification object:task userInfo:userInfo];
});
});
} else {
dispatch_async(url_session_manager_processing_queue(), ^{
NSError *serializationError = nil;
responseObject = [manager.responseSerializer responseObjectForResponse:task.response data:data error:&serializationError];
if (self.downloadFileURL) {
responseObject = self.downloadFileURL;
}
if (responseObject) {
userInfo[AFNetworkingTaskDidCompleteSerializedResponseKey] = responseObject;
}
if (serializationError) {
userInfo[AFNetworkingTaskDidCompleteErrorKey] = serializationError;
}
dispatch_group_async(manager.completionGroup ?: url_session_manager_completion_group(), manager.completionQueue ?: dispatch_get_main_queue(), ^{
if (self.completionHandler) {
self.completionHandler(task.response, responseObject, serializationError);
}
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidCompleteNotification object:task userInfo:userInfo];
});
});
});
}
}
#if AF_CAN_INCLUDE_SESSION_TASK_METRICS
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics AF_API_AVAILABLE(ios(10), macosx(10.12), watchos(3), tvos(10)) {
self.sessionTaskMetrics = metrics;
}
#endif
#pragma mark - NSURLSessionDataDelegate
- (void)URLSession:(__unused NSURLSession *)session
dataTask:(__unused NSURLSessionDataTask *)dataTask
didReceiveData:(NSData *)data
{
self.downloadProgress.totalUnitCount = dataTask.countOfBytesExpectedToReceive;
self.downloadProgress.completedUnitCount = dataTask.countOfBytesReceived;
[self.mutableData appendData:data];
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
didSendBodyData:(int64_t)bytesSent
totalBytesSent:(int64_t)totalBytesSent
totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend{
self.uploadProgress.totalUnitCount = task.countOfBytesExpectedToSend;
self.uploadProgress.completedUnitCount = task.countOfBytesSent;
}
#pragma mark - NSURLSessionDownloadDelegate
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didWriteData:(int64_t)bytesWritten
totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite{
self.downloadProgress.totalUnitCount = totalBytesExpectedToWrite;
self.downloadProgress.completedUnitCount = totalBytesWritten;
}
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didResumeAtOffset:(int64_t)fileOffset
expectedTotalBytes:(int64_t)expectedTotalBytes{
self.downloadProgress.totalUnitCount = expectedTotalBytes;
self.downloadProgress.completedUnitCount = fileOffset;
}
- (void)URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location
{
self.downloadFileURL = nil;
if (self.downloadTaskDidFinishDownloading) {
self.downloadFileURL = self.downloadTaskDidFinishDownloading(session, downloadTask, location);
if (self.downloadFileURL) {
NSError *fileManagerError = nil;
if (![[NSFileManager defaultManager] moveItemAtURL:location toURL:self.downloadFileURL error:&fileManagerError]) {
[[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDownloadTaskDidFailToMoveFileNotification object:downloadTask userInfo:fileManagerError.userInfo];
} else {
[[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDownloadTaskDidMoveFileSuccessfullyNotification object:downloadTask userInfo:nil];
}
}
}
}
@end