1. 程式人生 > >AFNetWorking網路連線詳解

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;

然後呼叫startMonitoringstopMonitoring來開始和結束監測,在中間網路狀態變化的過程中,你可以通過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轉成相應型別,然後返回出去