AFNetWorking網路連線詳解
AFHTTPSessionManager繼承於AFURLSessionManager,提供了更方便的HTTP請求方法,包括了GET、POST、PUT、PATCH、DELETE這五種方式,並且AF鼓勵我們在AFHTTPSessionManager再進行一次封裝來滿足我們自己的業務需求
在開始的地方,AF一直提醒到一個變數baseURL,這個變數你可以在進一步封裝的時候,將baseURL寫成你自己的HTTP請求原始地址,比如
+ (NSURL *)baseURL {
return [NSURL URLWithString:kBaseURLString];
}
在對baseURL進行拼接的時候,也需要注意一下幾點,防止出現請求的URL出現問題
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/
在初始化的方法裡面,我們看到這個方法
- (instancetype)initWithBaseURL:(nullable NSURL *)url
sessionConfiguration:(nullable NSURLSessionConfiguration *)configuration NS_DESIGNATED_INITIALIZER;
NS_DESIGNATED_INITIALIZER這個是什麼意思呢?它是表明該類有多種初始化的方法,在加上這個標記後,在系統的init方法裡面一定要呼叫該方法
- (nullable NSURLSessionDataTask *)POST:(NSString *)URLString parameters:(nullable id)parameters success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure DEPRECATED_ATTRIBUTE;
DEPRECATED_ATTRIBUTE這個相信大家見得比較多了,字面意思就是這個API不建議開發者再使用了,再使用時,會出現編譯警告
下面POST、GET、PUT、PATCH、DELETE方法傳參基本都是大同小異
URLString表示請求的URL,parameters表示客戶端請求內容的儲存器,progress表示請求的進度,constructingBodyWithBlock裡面只有一個formData用來拼接到HTTP的請求體,success表示請求成功後的block回撥,failure表示請求失敗的block回撥
那麼這幾個請求有什麼區別呢? 1、POST請求是向服務端傳送資料的,用來更新資源資訊,它可以改變資料的種類等資源 2、GET請求是向服務端發起請求資料,用來獲取或查詢資源資訊 3、PUT請求和POST請求很像,都是傳送資料的,但是PUT請求不能改變資料的種類等資源,它只能修改內容 4、DELETE請求就是用來刪除某個資源的 5、PATCH請求和PUT請求一樣,也是用來進行資料更新的,它是HTTP verb推薦用於更新的
在實際開發過程中,我們還是使用POST和GET請求是最多的
在請求實現的部分,都是呼叫了自己的一個方法
- (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(id)parameters
uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgress
downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgress
success:(void (^)(NSURLSessionDataTask *, id))success
failure:(void (^)(NSURLSessionDataTask *, NSError *))failure;
傳參的內容基本都是和上一層方法一樣,method指的就是請求的型別
NSError *serializationError = nil;
NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];
if (serializationError) {
if (failure) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{
failure(nil, serializationError);
});
#pragma clang diagnostic pop
}
return nil;
}
__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(dataTask, error);
}
} else {
if (success) {
success(dataTask, responseObject);
}
}
}];
return dataTask;
內部的實現則是先根據傳入的URLString建立一個request物件,然後呼叫父類的dataTaskWithRequest方法生成dataTask任務,奏是這麼簡單
AFNetworkReachabilityManager是監測網路狀態的類,狀態值有以下四種
typedef NS_ENUM(NSInteger, AFNetworkReachabilityStatus) {
AFNetworkReachabilityStatusUnknown = -1,//未知
AFNetworkReachabilityStatusNotReachable = 0,//不可用
AFNetworkReachabilityStatusReachableViaWWAN = 1,//無線廣域網連線
AFNetworkReachabilityStatusReachableViaWiFi = 2,//WiFi連線
};
你可以通過域名或者socket地址來例項化物件,也可以通過建立一個SCNetworkReachabilityRef物件來初始化物件
+ (instancetype)managerForDomain:(NSString *)domain;
+ (instancetype)managerForAddress:(const void *)address;
- (instancetype)initWithReachability:(SCNetworkReachabilityRef)reachability NS_DESIGNATED_INITIALIZER;
然後呼叫startMonitoring和stopMonitoring來開始和結束監測,在中間網路狀態變化的過程中,你可以通過setReachabilityStatusChangeBlock來獲得網路的狀態,也可以通過註冊通知的形式,接收網路狀態
- (void)startMonitoring {
[self stopMonitoring];
if (!self.networkReachability) {
return;
}
__weak __typeof(self)weakSelf = self;
AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status) {
__strong __typeof(weakSelf)strongSelf = weakSelf;
strongSelf.networkReachabilityStatus = status;
if (strongSelf.networkReachabilityStatusBlock) {
strongSelf.networkReachabilityStatusBlock(status);
}
};
SCNetworkReachabilityContext context = {0, (__bridge void *)callback, AFNetworkReachabilityRetainCallback, AFNetworkReachabilityReleaseCallback, NULL};
SCNetworkReachabilitySetCallback(self.networkReachability, AFNetworkReachabilityCallback, &context);
SCNetworkReachabilityScheduleWithRunLoop(self.networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0),^{
SCNetworkReachabilityFlags flags;
if (SCNetworkReachabilityGetFlags(self.networkReachability, &flags)) {
AFPostReachabilityStatusChange(flags, callback);
}
});
}
在開啟監測網路狀態的時候,首先設定好自己網路狀態的block回撥,然後建立一個SCNetworkReachabilityContext結構體,第一個引數version是版本號,值為0,第二個引數info是一個指向資料block回撥的c指標,第三個引數retain則是通過一個回撥來再保留資料一次,它的值可能為null,第四個引數release則是通過回撥移除,第五個引數description就是提供資料的描述。然後設定回撥,把它加到runloop裡面對它進行監測,並且在後臺執行緒發現變化時,傳送網路狀態變化
AFSecurityPolicy是安全策略類,有三種SSL Pinning模式
typedef NS_ENUM(NSUInteger, AFSSLPinningMode) {
AFSSLPinningModeNone,//沒有安全策略
AFSSLPinningModePublicKey,//公鑰
AFSSLPinningModeCertificate,//證書
};
@property (nonatomic, strong, nullable) NSSet <NSData *> *pinnedCertificates;
這個是證書集合,泛型裡面表示了集合裡面是NSData型別,表明這個是用來存證書資料的集合,這些證書根據SSL Pinning模式來和伺服器進行校驗,預設是沒有證書的,我們需要呼叫certificatesInBundle:方法將bundle裡面的證書檔案轉成裡面是data型別的集合
+ (instancetype)defaultPolicy;
+ (instancetype)policyWithPinningMode:(AFSSLPinningMode)pinningMode;
+ (instancetype)policyWithPinningMode:(AFSSLPinningMode)pinningMode withPinnedCertificates:(NSSet <NSData *> *)pinnedCertificates;
有三種例項化的方法,一種是預設策略,AFSSLPinningModeNone,第二種是自定義一個安全策略,然後獲取當前類的bundle,讀取cer檔案生成集合,第三種則是需要我們傳入證書集合
AFURLRequestSerialization是用來對URL請求做一些處理
將URL裡面的特殊字元替換成百分號:
FOUNDATION_EXPORT NSString * AFPercentEscapedStringFromString(NSString *string);
將字典裡面的key/value值組裝成%@=%@並以&區分的格式:
FOUNDATION_EXPORT NSString * AFQueryStringFromParameters(NSDictionary *parameters);
在AFHTTPRequestSerializer裡面有設定頭資訊的方法
- (void)setValue:(nullable NSString *)value
forHTTPHeaderField:(NSString *)field;
在這裡value為空時,會被當做刪除處理,會刪除已存在的請求頭,不會空時,會增加新的請求頭或者設定已存在的請求頭
- (void)setAuthorizationHeaderFieldWithUsername:(NSString *)username
password:(NSString *)password;
將使用者名稱、密碼作為資訊,設定為請求頭,裡面先將使用者名稱密碼拼好轉成data,再通過base64編碼的形式轉成字串,再設定為請求頭的內容
建立請求的方法有三種:
- (NSMutableURLRequest *)requestWithMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(nullable id)parameters
error:(NSError * _Nullable __autoreleasing *)error;
- (NSMutableURLRequest *)multipartFormRequestWithMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(nullable NSDictionary <NSString *, id> *)parameters
constructingBodyWithBlock:(nullable void (^)(id <AFMultipartFormData> formData))block
error:(NSError * _Nullable __autoreleasing *)error;
- (NSMutableURLRequest *)requestWithMultipartFormRequest:(NSURLRequest *)request
writingStreamContentsToFile:(NSURL *)fileURL
completionHandler:(nullable void (^)(NSError * _Nullable error))handler;
method指的是請求的方法,比如GET、POST等,URLString是用來建立請求URL的字串,parameters是GET請求的查詢欄位,或者是請求的HTTP體,request是HTTPBodyStream裡面的例項變數request,fileURL是檔案URL
- (BOOL)appendPartWithFileURL:(NSURL *)fileURL
name:(NSString *)name
error:(NSError * _Nullable __autoreleasing *)error;
- (BOOL)appendPartWithFileURL:(NSURL *)fileURL
name:(NSString *)name
fileName:(NSString *)fileName
mimeType:(NSString *)mimeType
error:(NSError * _Nullable __autoreleasing *)error;
根據檔案資料來拼接HTTP頭,fileURL是檔案的URL,name指的是資料的名字,fileName是檔案的名字,可以根據fileURL來獲取到最後一部分的檔名,mimeType是檔案資料的mime型別,可以根據檔案的字尾呼叫AFContentTypeForPathExtension方法來獲得
static inline NSString * AFContentTypeForPathExtension(NSString *extension) {
NSString *UTI = (__bridge_transfer NSString *)UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)extension, NULL);
NSString *contentType = (__bridge_transfer NSString *)UTTypeCopyPreferredTagWithClass((__bridge CFStringRef)UTI, kUTTagClassMIMEType);
if (!contentType) {
return @"application/octet-stream";
} else {
return contentType;
}
}
首先根據字尾來建立一個型別標識,然後再將型別標識轉成mime型別,如果有對應的型別,則返回application/octet-stream,否則直接返回contentType
- (void)throttleBandwidthWithPacketSize:(NSUInteger)numberOfBytes
delay:(NSTimeInterval)delay;
當在3G或者E網路環境下時,在流請求時,可能會出現上傳失敗的情況,所以我們可以通過設定請求的頻寬以及延遲時間來解決問題,numberOfBytes是位元組大小,預設是16kb,delay是延遲時間,預設是沒有延遲
在實現檔案裡面,有一個建立多部分組成的邊界符這個方法
static NSString * AFCreateMultipartFormBoundary() {
return [NSString stringWithFormat:@"Boundary+%08X%08X", arc4random(), arc4random()];
}
這裡用到一個生成隨機數的方法arc4random(),oc裡面還有一個生成隨機數的方法random(),那這兩個方法有什麼區別呢? 首先arc4random()的取值範圍是0x100000000 (4294967296),random()是0x7fffffff (2147483647),前者是後者的兩倍,在精度上面優於後者,而且在使用random()的時候,需要自己先生成一個隨機種子,但arc4random()在第一次呼叫的時候就自動生成了,在使用上面也比較方便
AFURLResponseSerialization中定義了一個協議
- (nullable id)responseObjectForResponse:(nullable NSURLResponse *)response
data:(nullable NSData *)data
error:(NSError * _Nullable __autoreleasing *)error NS_SWIFT_NOTHROW;
用來處理不同型別的響應解析,在裡面各個類都實現了該協議
AFJSONResponseSerializer預設接受這三個MIME型別
- `application/json`
- `text/json`
- `text/javascript`
AFXMLParserResponseSerializer預設接受這兩個MIME型別
- `application/xml`
- `text/xml`
AFPropertyListResponseSerializer預設接受這一個MIME型別
- `application/x-plist`
AFImageResponseSerializer預設接受這十個MIME型別
- `image/tiff`
- `image/jpeg`
- `image/gif`
- `image/png`
- `image/ico`
- `image/x-icon`
- `image/bmp`
- `image/x-bmp`
- `image/x-xbitmap`
- `image/x-win-bitmap`
在AFHTTPResponseSerializer實現檔案裡面
- (BOOL)validateResponse:(NSHTTPURLResponse *)response
data:(NSData *)data
error:(NSError * __autoreleasing *)error
{
BOOL responseIsValid = YES;
NSError *validationError = nil;
if (response && [response isKindOfClass:[NSHTTPURLResponse class]]) {
if (self.acceptableContentTypes && ![self.acceptableContentTypes containsObject:[response MIMEType]]) {
if ([data length] > 0 && [response URL]) {
NSMutableDictionary *mutableUserInfo = [@{
NSLocalizedDescriptionKey: [NSString stringWithFormat:NSLocalizedStringFromTable(@"Request failed: unacceptable content-type: %@", @"AFNetworking", nil), [response MIMEType]],
NSURLErrorFailingURLErrorKey:[response URL],
AFNetworkingOperationFailingURLResponseErrorKey: response,
} mutableCopy];
if (data) {
mutableUserInfo[AFNetworkingOperationFailingURLResponseDataErrorKey] = data;
}
validationError = AFErrorWithUnderlyingError([NSError errorWithDomain:AFURLResponseSerializationErrorDomain code:NSURLErrorCannotDecodeContentData userInfo:mutableUserInfo], validationError);
}
responseIsValid = NO;
}
if (self.acceptableStatusCodes && ![self.acceptableStatusCodes containsIndex:(NSUInteger)response.statusCode] && [response URL]) {
NSMutableDictionary *mutableUserInfo = [@{
NSLocalizedDescriptionKey: [NSString stringWithFormat:NSLocalizedStringFromTable(@"Request failed: %@ (%ld)", @"AFNetworking", nil), [NSHTTPURLResponse localizedStringForStatusCode:response.statusCode], (long)response.statusCode],
NSURLErrorFailingURLErrorKey:[response URL],
AFNetworkingOperationFailingURLResponseErrorKey: response,
} mutableCopy];
if (data) {
mutableUserInfo[AFNetworkingOperationFailingURLResponseDataErrorKey] = data;
}
validationError = AFErrorWithUnderlyingError([NSError errorWithDomain:AFURLResponseSerializationErrorDomain code:NSURLErrorBadServerResponse userInfo:mutableUserInfo], validationError);
responseIsValid = NO;
}
}
if (error && !responseIsValid) {
*error = validationError;
}
return responseIsValid;
}
驗證response有三個步驟: 第一步先檢查response是否為空和判斷response是否是NSHTTPURLResponse類,如果不符合上述條件的話,但是返回的是YES有效的,這個我有點不太理解,後面如果有找到解答的話我會更新上來 第二步檢查response MIME型別是否屬於接受的型別,如果沒有的話,產生error 第三步檢查接受的狀態碼是否存在,如果沒有的話,產生潛在的error,放在error的userInfo裡面,key是NSUnderlyingErrorKey
- (id)responseObjectForResponse:(NSURLResponse *)response
data:(NSData *)data
error:(NSError *__autoreleasing *)error
{
[self validateResponse:(NSHTTPURLResponse *)response data:data error:error];
return data;
}
這裡只是呼叫了下validateResponse:data:error:方法,實際上返回的就是傳入的data,目的是讓子類自己去實現responseObjectForResponse:data:error: 子類的實現responseObjectForResponse:data:error:也都是先校驗response的有效性,然後將data轉成相應型別,然後返回出去