AFNetworking框架分析(二)——AFURLSessionManager(上)

AFURLSessionManager 這個類是AFN框架的核心類,基本上通過它來實現(xiàn)了大部分核心功能。負責(zé)請求的建立、管理、銷毀、安全、請求重定向、請求重啟等各種功能。他主要實現(xiàn)了NSURLSession和NSRULSessionTask的封裝。
首先來對比下系統(tǒng)URLSession網(wǎng)絡(luò)請求與AFN網(wǎng)絡(luò)請求的方法

系統(tǒng)提供的URLSession網(wǎng)絡(luò)POST請求方法使用

    NSURL *securl                   = [NSURL URLWithString:authenticate_appAuthenticate];
    NSMutableURLRequest *secrequest = [[NSMutableURLRequest alloc]initWithURL:securl
                                                                  cachePolicy:NSURLRequestReloadIgnoringCacheData
                                                              timeoutInterval:1000];
    
    [secrequest setValue:[MHXIMPAFOper hashRule:[MHXIMPAFOper getTimeStamp]
                                           uuid:[MHUniqueID uniqueCFUUID]]
      forHTTPHeaderField:@"X-IMPF"];
    
    [secrequest setHTTPMethod:@"POST"];
    SendObject *secsend  = [[SendObject alloc]init];
    NSString *secbodyStr = [[secsend getSend] yy_modelToJSONString];
    NSData *secbody      = [secbodyStr dataUsingEncoding:NSUTF8StringEncoding];
    [secrequest setHTTPBody:secbody];
    NSURLSession *requestSessions = [NSURLSession sharedSession];
    NSURLSessionDataTask *requestTasks = [requestSessions dataTaskWithRequest:secrequest completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        NSLog(@"data ======= %@, response ======= %@, error ======= %@",data ,response, error);

    }];
    [requestTasks resume];

AFN中POST方法使用(這里進行了一層簡單的封裝)

+(AFHTTPSessionManager *)httpManager{
    // 獲取請求對象
    AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
    // 設(shè)置請求格式
    manager.requestSerializer = [AFJSONRequestSerializer serializer];
    // 設(shè)置返回格式
    manager.responseSerializer = [AFJSONResponseSerializer serializer];
    manager.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"text/plain", @"multipart/form-data", @"application/json", @"text/html", @"image/jpeg", @"image/png", @"application/octet-stream", @"text/json", nil];
    [manager.requestSerializer setValue:@"application/json" forHTTPHeaderField:@"Accept"];
    [manager.requestSerializer setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
    manager.requestSerializer.timeoutInterval = kRequestTimeOut;
    return manager;
}

+(__kindof NSURLSessionTask *)Post:(NSString *)URLString parameters:(id)parameters success:(void(^)(id responseObject))success failure:(void(^)(NSError *))failure{
    AFHTTPSessionManager *manager = [self httpManager];
    return [manager POST:URLString parameters:parameters progress:^(NSProgress * _Nonnull uploadProgress) {
        
    } success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
        if (success) {
            success(responseObject);
        }
    } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
        if (failure) {
            failure(error);
        }
    }];
}

通過對比可以發(fā)現(xiàn),AFN幫我們將繁瑣的網(wǎng)絡(luò)請求進行了封裝,只需要傳入訪問服務(wù)器URL以及參數(shù),就可以通過block返回給對應(yīng)的正確或者錯誤數(shù)據(jù),而且還可以通過progress的block不斷獲取進度。
AFHTTPSessionManager中一共提供了四種初始化方法,但最終都會指向調(diào)用- (instancetype)initWithBaseURL:(NSURL *)url sessionConfiguration:(NSURLSessionConfiguration *)configuration方法

- (instancetype)initWithBaseURL:(NSURL *)url
           sessionConfiguration:(NSURLSessionConfiguration *)configuration
{
    self = [super initWithSessionConfiguration:configuration];
    if (!self) {
        return nil;
    }

    // Ensure terminal slash for baseURL path, so that NSURL +URLWithString:relativeToURL: works as expected
    if ([[url path] length] > 0 && ![[url absoluteString] hasSuffix:@"/"]) {
        url = [url URLByAppendingPathComponent:@""];
    }

    self.baseURL = url;

    self.requestSerializer = [AFHTTPRequestSerializer serializer];
    self.responseSerializer = [AFJSONResponseSerializer serializer];

    return self;
}

初始化時,調(diào)用了父類的方法[super initWithSessionConfiguration:configuration]

- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration {
    self = [super init];
    if (!self) {
        return nil;
    }
    //設(shè)置默認的configuration,配置session
    if (!configuration) {
        configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
    }
    //當前實例變量持有configuration
    self.sessionConfiguration = configuration;

    //設(shè)置delegate的操作隊列并發(fā)線程數(shù)量為1,即串行隊列
    self.operationQueue = [[NSOperationQueue alloc] init];
    self.operationQueue.maxConcurrentOperationCount = 1;

    /*
     
     */
    self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];
    
    //默認為JSON解析
    self.responseSerializer = [AFJSONResponseSerializer serializer];
    
    //設(shè)置默認證書,無條件信任HTTPS認證
    self.securityPolicy = [AFSecurityPolicy defaultPolicy];

#if !TARGET_OS_WATCH
    //網(wǎng)絡(luò)狀態(tài)監(jiān)聽
    self.reachabilityManager = [AFNetworkReachabilityManager sharedManager];
#endif

    //存放一個 delegate = value,taskId = key 的字典
    self.mutableTaskDelegatesKeyedByTaskIdentifier = [[NSMutableDictionary alloc] init];

    //使用NSLock確保線程安全
    self.lock = [[NSLock alloc] init];
    self.lock.name = AFURLSessionManagerLockName;

    //異步獲取當前session所有未完成的task。會讓后臺任務(wù)重新回來初始化session,之前可能存在的任務(wù)進行置空處理
    //參考AFN問題匯總https://github.com/AFNetworking/AFNetworking/issues/3499
    [self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
        for (NSURLSessionDataTask *task in dataTasks) {
            [self addDelegateForDataTask:task uploadProgress:nil downloadProgress:nil completionHandler:nil];
        }

        for (NSURLSessionUploadTask *uploadTask in uploadTasks) {
            [self addDelegateForUploadTask:uploadTask progress:nil completionHandler:nil];
        }

        for (NSURLSessionDownloadTask *downloadTask in downloadTasks) {
            [self addDelegateForDownloadTask:downloadTask progress:nil destination:nil completionHandler:nil];
        }
    }];

    return self;
}

從上述代碼中可以看出,在AFN初始化過程中,除了對NSURLSession進行初始化之外,還設(shè)置了許多默認配置,例如創(chuàng)建串行隊列、默認JSON解析、無條件信任證書、保證線程安全、網(wǎng)絡(luò)狀態(tài)監(jiān)聽以及舊任務(wù)置空處理等操作。

以POST請求為例,調(diào)用的
- (nullable NSURLSessionDataTask *)POST:(NSString *)URLString parameters:(nullable id)parameters progress:(nullable void (^)(NSProgress *uploadProgress))uploadProgress success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure方法
進入該方法查看實現(xiàn),發(fā)現(xiàn)除了常用的POST和GET請求之外,還有PUT、HEAD、DELETE、PATCH這些其它請求類型的方法實現(xiàn),都執(zhí)行了同一個方法
- (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
在該方法中,首先調(diào)用AFHTTPRequestSerializer的requestWithMethod函數(shù)構(gòu)建request;當有錯誤時,處理request構(gòu)建產(chǎn)生的錯誤 - serializationError;其中relativeToURL表示將URLString拼接至baseURL中。接下來,繼續(xù)查看requestSerializer的創(chuàng)建方法實現(xiàn)。

requestSerializer創(chuàng)建方法實現(xiàn)

其中最重要的部分,就是將request的各種屬性進行遍歷,用于給mutableRequest自帶的屬性賦值。
關(guān)于AFHTTPRequestSerializerObservedKeyPaths(),查看實現(xiàn)方法可以發(fā)現(xiàn)返回一個帶有方法名的數(shù)組,定義了一個static的方法,表示該方法只能在本文件中使用。利用runtime的反射調(diào)用NSStringFromSelector方法,將六個方法名轉(zhuǎn)換成字符串存入數(shù)組中。而這個六個方法又分別對應(yīng)了mutableRequest的六個屬性名稱。
AFHTTPRequestSerializerObservedKeyPaths()代碼實現(xiàn)

從上往下,依次代表:
1. 是否允許使用設(shè)備的蜂窩移動網(wǎng)絡(luò)來創(chuàng)建request,默認為允許;
2.創(chuàng)建的request所使用的緩存策略,默認使用NSURLRequestUseProtocolCachePolicy,該策略表示如果緩存不存在,直接從服務(wù)端獲取。如果緩存存在,會根據(jù)response中的Cache-Control字段判斷下一步操作,如: Cache-Control字段為must-revalidata, 則 詢問服務(wù)端該數(shù)據(jù)是否有更新,無更新直接返回給用戶緩存數(shù)據(jù),若已更新,則請求服務(wù)端.
3. 如果設(shè)置HTTPShouldHandleCookies為YES,就處理存儲在NSHTTPCookieStore中的cookies HTTPShouldHandleCookies表示是否應(yīng)該給request設(shè)置cookie并隨request一起發(fā)送出去
4.HTTPShouldUsePipelining表示receiver(理解為iOS客戶端)的下一個信息是否必須等到上一個請求回復(fù)才能發(fā)送。如果為YES表示可以,NO表示必須等receiver收到先前的回復(fù)才能發(fā)送下個信息
5.設(shè)定request的network service類型. 默認NSURLNetworkServiceTypeDefault. 這個network service是為了告訴系統(tǒng)網(wǎng)絡(luò)層這個request使用的目的。 比如NSURLNetworkServiceTypeVoIP表示的這個request是用來請求網(wǎng)際協(xié)議通話技術(shù)(Voice over IP)。系統(tǒng)能根據(jù)提供的信息來優(yōu)化網(wǎng)絡(luò)處理,從而優(yōu)化電池壽命,網(wǎng)絡(luò)性能等等,客戶端基本不使用
6.超時機制,默認60秒
至于要在此處實現(xiàn)遍歷的目的,是因為在AFHTTPRequestSerializer類的init方法中,AFN給上面指定的6個方法選擇器每一個元素添加了KVO觀察者
self為自己的方法添加觀察者

KVO代理實現(xiàn)

當數(shù)組的元素發(fā)生變化時,判斷新值是否為空。若為空則從mutableObservedChangedKeyPaths可變集合中移除;不為空,則添加至可變集合中。
小提示:關(guān)于NSNull,包含了唯一方法+(NSNull *)null,是一個對象,用于表示零值的單獨對象。主要用于不能使用nil的場景下,例如可變數(shù)組中,想插入一個空對象的情況。
因此,在AFHTTPSessionManager初始完成之后,需要額外添加自定義的request配置時,比如超時時間設(shè)置為10秒。這時KVO監(jiān)聽到timeoutInterval的屬性發(fā)生變化,將keyPath添加到mutableObservedChangedKeyPaths可變集合中。然后,在執(zhí)行網(wǎng)絡(luò)請求方法時,會遍歷該可變集合,通過KVC動態(tài)的給mutableRequest添加value,最終實現(xiàn)將自定義配置添加至request中。
而且在AFHTTPRequestSerializer類的初始化方法中,AFN自動添加了網(wǎng)絡(luò)請求頭部內(nèi)容
請求頭部內(nèi)容打印

接下來,到了AFN的重點,對傳入的參數(shù)字典進行處理。
使用AFN傳入的參數(shù)格式為字典,但在網(wǎng)絡(luò)請求中,是要轉(zhuǎn)換成key=value&key=value的形式(GET請求直接拼接到URL之后,POST請求放入request body中),才能傳給服務(wù)端獲取有效的數(shù)據(jù)。

將請求參數(shù)字典轉(zhuǎn)化成字符串

NSString * AFQueryStringFromParameters(NSDictionary *parameters)C函數(shù)的作用,是遍歷數(shù)組中的AFQueryStringPair,然后以&符號拼接。AFQueryStringPair是一個數(shù)據(jù)處理類,只有兩個屬性:field和value;一個方法:-URLEncodedStringValue。它的作用就是上面我們說的,以key=value的形式,然后用URL Encode編碼,拼接成字符串。
在遍歷過程中,最終指向了NSArray * AFQueryStringPairsFromKeyAndValue(NSString *key, id value)函數(shù),為了保證參數(shù)字典中,所有的value類型確保為字符串類型,這里便使用了遞歸,針對value可能為字典、數(shù)組、集合的類型時進行解析。例如,之前的項目中當需要向后臺傳輸一段用戶的定位數(shù)據(jù)用于分析行動軌跡時,傳參的字典中的location的key值嵌套了一個包含分別以經(jīng)度和緯度為key的字典。
遞歸確保所有的value最終全部轉(zhuǎn)換成字符串形式

當value為字典、數(shù)組、集合的類型時,會執(zhí)行遞歸解析,直到value類型都不為上述類型時,向mutableQueryStringComponents添加一個AFQueryStringPair類的對象,其中傳入key和value并返回出去。
(這里AFN框架執(zhí)行了升序排列,這里不是很明白為什么要先將字典的key進行升序排列再進行數(shù)據(jù)遞歸解析)
當傳參字典中所有數(shù)據(jù)解析完成之后,會通過遍歷返回為AFQueryStringPair類的對象,然后將該對象進行百分號編碼,用于處理可能存在包含歧義或者不符合規(guī)劃的字符(可以自行查找關(guān)于百分號編碼相關(guān)資料),最后將字符串拼接"&"符號。
以上,就是AFN框架中,發(fā)起網(wǎng)絡(luò)請求之前,關(guān)于request處理相關(guān)的操作全部流程分析,其中最主要的功能就是對傳參參數(shù)進行了數(shù)據(jù)的遞歸解析,其次對request的六個相關(guān)屬性進行KVO監(jiān)聽,可以在初始化AFHTTPSessionManager對象之后,自定義修改指定request屬性時,通過KVO代理方法以及動態(tài)KVC最終在request中實現(xiàn)配置修改。


該文章首次發(fā)表在 簡書:我只不過是出來寫寫代碼 博客,并自動同步至 騰訊云:我只不過是出來寫寫iOS 博客

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容