91超碰碰碰碰久久久久久综合_超碰av人澡人澡人澡人澡人掠_国产黄大片在线观看画质优化_txt小说免费全本

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

AFNetworking到底長啥樣(下)

發布時間:2020-07-27 09:34:35 來源:網絡 閱讀:415 作者:zlayne 欄目:移動開發

在AFNetworking到底長啥樣(上)中簡單介紹了AFN涉及的主要類及其結構,接下來以一個簡單的POST請求探尋其內部是如何實現的。

一、環境搭建

  1. 服務器配置

    本例中直接使用iMac自帶的Apache,并為其開啟PHP支持。在服務器目錄下編寫index.php文件如下:

    <?php
    echo @"This is Layne's Response";
    ?>
  2. 編寫測試App

    創建一個測試App,在主界面上增加一個按鈕,在按鈕的點擊函數中發起網絡請求,如下:

    - (AFHTTPSessionManager *)manager{//lazy
       if(!_manager){
           _manager = [AFHTTPSessionManager manager];
       }
       return _manager;
    }
    
    - (void)click{
       [self.manager POST:@"http://www.layne.com" 
               parameters:@{@"name":@"layne",@"age":@30} 
                  headers:@{@"TestName":@"myTest"} 
                 progress:nil 
            success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
                    NSLog(@"success:%@",responseObject);
            } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
                    NSLog(@"fail:%@",error);
       }];
    }

二、一個POST請求的前世今生

啟動測試App,點擊按鈕。接下里讓我們看看AFN是如何優雅的管理網絡請求的。

1.初始化

  • AFHTTPSessionManager的初始化

    self.managerAFHTTPSessionManager的實例,它使用類方法+ manager初始化。這個類方法最終調用如下方法:

    - (instancetype)initWithBaseURL:(NSURL *)url
             sessionConfiguration:(NSURLSessionConfiguration *)configuration
    {
      self = [super initWithSessionConfiguration:configuration];//①configuration=nil
      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;
    }

    ①中調用父類的方法初始化父類相關屬性,這包括:

    operationQueue
    responseSerializer(為AFJSONResponseSerializer實例)
    securityPolicy(為默認,即不進行SSL驗證)
    reachabilityManager
    mutableTaskDelegatesKeyedByTaskIdentifier(管理{taskID:delegate})
    lock(用于操作mutableTaskDelegatesKeyedByTaskIdentifier時保證線程安全)
    session(NSURLSession實例,設置其代理為self,queue為operationQueue)

    ②中設置其requestSerializer為AFHTTPRequestSerializer實例,③重設其responseSerializer為AFJSONResponseSerializer實例。

  • AFHTTPRequestSerializer的初始化

    requestSerializer被設置成了AFHTTPRequestSerializer的實例,初始化是通過類方法+serializer實現的。內容包括:

    stringEncoding(為NSUTF8StringEncoding)
    mutableHTTPRequestHeaders(保存用戶設置的header,默認會添加"Accept-Language"和"User-Agent",其中"User-Agent"還進行了ICU轉換以保證必須為ASCII字符)
    HTTPMethodsEncodingParametersInURI(為GET、HEAD、DELETE)
    mutableObservedChangedKeyPaths (監聽"allowsCellularAccess"、"cachePolicy"、"HTTPShouldHandleCookies"、"HTTPShouldUsePipelining"、"networkServiceType"、"timeoutInterval",一旦用戶設置了這6個屬性,則對應屬性的名會被存入,最后其值會加到request中去,本質是增量添加:“誰改變添加誰”)
  • AFJSONResponseSerializer的初始化

    responseSerializer被設置成為了AFJSONResponseSerializer的實例,初始化是通過類方法+serializer實現的。內容包括:

    readingOptions(為0)
    acceptableStatusCodes(可接受的code,初始化為[200~199])
    acceptableContentTypes(可接受的contenttype,設置為application/json、text/json、text/javascript)
    注:其父類AFHTTPResponseSerializer的acceptableContentTypes=nil,即接受任何Content。

至此必要的初始化已完成。

2.發起請求

(1) POST

進入到POST函數中:

- (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 *dataTask = [self dataTaskWithHTTPMethod:@"POST" URLString:URLString parameters:parameters headers:headers uploadProgress:uploadProgress downloadProgress:nil success:success failure:failure];

    [dataTask resume];

    return dataTask;
}

即最終生成一個dataTask,然后resume啟動,并將dataTask返回。

(2) 生成dataTask

生成的dataTask是通過如下函數實現的,本例中傳入的參數值也已標明:

-  dataTaskWithHTTPMethod:  //@"POST"
                URLString:  //@"http://www.layne.com"
               parameters:  //@{@"name":@"layne",@"age":@30}
                  headers:  //@{@"TestName":@"myTest"} 
           uploadProgress:  //nil
         downloadProgress:  //nil
                  success:  //successBlock{NSLog(@"success:%@",responseObject);}
                  failure:  //failureBlock{NSLog(@"fail:%@",error);}

其內部執行的步驟如下:

Step1:調用requestSerializer的如下方法創建mutableRequest:

- (NSMutableURLRequest *)requestWithMethod:(NSString *)method   //@"POST"
                                 URLString:(NSString *)URLString//@"http://www.layne.com"
                                parameters:(id)parameters//@{@"name":@"layne",@"age":@30}
                                     error:(NSError *__autoreleasing *)error
{
    NSParameterAssert(method);
    NSParameterAssert(URLString);

    NSURL *url = [NSURL URLWithString:URLString];

    NSParameterAssert(url);

    NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url];
    mutableRequest.HTTPMethod = method;//設置為POST

    for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
        if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) {
            [mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];
        }
    }//根據用戶是否設置了allowsCellularAccess、cachePolicy、HTTPShouldHandleCookies、HTTPShouldUsePipelining、networkServiceType、timeoutInterval來設置mutableRequest對應字段值。本例中由于未進行任何設置,因此上述邏輯不會有效果。

    mutableRequest = [[self requestBySerializingRequest:mutableRequest withParameters:parameters error:error] mutableCopy];//①

    return mutableRequest;
}

①是調用AFURLRequestSerialization協議方法對mutableRequest進行進一步處理,包括:

  • 設置默認header,如在requestSerializer初始化時設置默認header:User-AgentAccept-Language

    [self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
          if (![request valueForHTTPHeaderField:field]) {
              [mutableRequest setValue:value forHTTPHeaderField:field];
          }
    }];
  • 處理參數

    若用戶自定義了處理參數的block(self.queryStringSerialization),則使用該block;否則使用默認的處理方式,會被最終處理成如下格式:name=layne&age=30

  • 根據HTTPMethod決定參數串放到哪里:GET/HEAD/DELETE直接拼接到url;其他(如POST)方法放到HTTPBody中(使用stringEncoding(NSUTF8StringEncoding)編碼),并設置Content-Typeapplication/x-www-form-urlencoded

至此,生成的mutableRequest結構如下:

mutableRequest{
URL:          "http://www.layne.com"
Method:       "POST"
Header:       "Accept-Language" = "en;q=1";
              "User-Agent" = "TestApp/1.0 (iPhone; iOS 13.1; Scale/3.00)";
              "Content-Type" = "application/x-www-form-urlencoded";
HttpBody      <6167653d 3330266e 616d653d 6c61796e 65>  //name=layne&age=30(utf-8)
}

Step2:合并傳入的header

for (NSString *headerField in headers.keyEnumerator) {
        [request addValue:headers[headerField] forHTTPHeaderField:headerField];
}

執行之后mutableRequest結構如下:

mutableRequest{
URL:          "http://www.layne.com"
Method:       "POST"
Header:       "Accept-Language" = "en;q=1";
              "User-Agent" = "TestApp/1.0 (iPhone; iOS 13.1; Scale/3.00)";
              "Content-Type" = "application/x-www-form-urlencoded";
              "TestName":"myTest";
HttpBody      <6167653d 3330266e 616d653d 6c61796e 65>  //name=layne&age=30(utf-8)
}

Step3:判斷Step1中生成mutableRequest的過程是否出錯,若出錯,則調用failureBlock并返回。

if (serializationError) {
        if (failure) {
            dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{
                failure(nil, serializationError);
            });
        }
        return nil;
 }

Step4:根據mutableRequest生成dataTask。

- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request //request
                               uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock//nil
                             downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock//nil
                            completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject,  NSError * _Nullable error))completionHandler /*block{
                                  if (error) {
                                      if (failure) {
                                          failure(dataTask, error);
                                      }
                                  } else {
                                      if (success) {
                                          success(dataTask, responseObject);
                                      }
                                  }        
                                             }*/
{

    __block NSURLSessionDataTask *dataTask = nil;
    url_session_manager_create_task_safely(^{ //①
        dataTask = [self.session dataTaskWithRequest:request];
    });

    [self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler];//②

    return dataTask;//③
}

說明:

①中不直接生成dataTask,是因為在iOS8以下的系統上,若是在并行隊列上創建dataTask會導致completionHandler調用出錯。因此為了解決這個問題,針對iOS8以下系統,AFN使用自己維護的一個串行隊列來創建dataTask。具體問題描述如下:

Due to this bug: http://openradar.appspot.com/radar?id=5871104061079552 in NSURLSessionTask, creating tasks on a concurrent queue can cause incorrect completionHandlers to get called.

When a duplicate taskIdentifier is returned by the task, the previous completionHandler gets cleared out and replaced with the new one. If the data for the first request comes back before the second request's data, the first response is then called against the second completionHandler.

I'm not sure what AFNetworking should do here — it could enforce creating tasks on a serial queue or it could just advise people to do so. We could also add a test to assert that the taskIdentifier is not a duplicate?

②的作用是為dataTask生成對應的delegate,以調用回調方法。

- (void)addDelegateForDataTask:(NSURLSessionDataTask *)dataTask //dataTask
                uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock//nil
              downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock//nil
             completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler/*block{
                                  if (error) {
                                      if (failure) {
                                          failure(dataTask, error);
                                      }
                                  } else {
                                      if (success) {
                                          success(dataTask, responseObject);
                                      }
                                  }        
                                             }*/
{
    AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] initWithTask:dataTask];//a
    delegate.manager = self;//b
    delegate.completionHandler = completionHandler;//c

    dataTask.taskDescription = self.taskDescriptionForSessionTasks;//d
    [self setDelegate:delegate forTask:dataTask];//e

    delegate.uploadProgressBlock = uploadProgressBlock;//nil
    delegate.downloadProgressBlock = downloadProgressBlock;//nil
}

a. 為dataTask生成AFURLSessionManagerTaskDelegate實例。delegate內部維護著:

一個mutableData用來保存收到的response data。

一個uploadProgress和downloadProgress用來標明上傳/下載進度。它們的cancel、suspend和resume操作與dataTask進行了綁定,即通過對它們進行cancel、suspend和resume操作就可以操作dataTask的cancel、suspend和resume。此外,還采用KVO監聽uploadProgress和downloadProgress的進度變化,從而調用用戶自定義的block:uploadProgressBlockdownloadProgressBlock

b. delegate弱引用當前的manager。

c.delegate保存完成回調。

③返回最終的dataTask,并啟動。

(3) 處理Response

AFN是基于NSURLSession的,涉及以下幾個協議:

NSURLSessionDelegate

 - URLSession:didBecomeInvalidWithError:
 - URLSession:didReceiveChallenge:completionHandler:
 - URLSessionDidFinishEventsForBackgroundURLSession:

NSURLSessionTaskDelegate

 - URLSession:willPerformHTTPRedirection:newRequest:completionHandler:
 - URLSession:task:didReceiveChallenge:completionHandler:
 - URLSession:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:
 - URLSession:task:needNewBodyStream:
 - URLSession:task:didCompleteWithError:
 - URLSession:task:didFinishCollectingMetrics:

NSURLSessionDataDelegate

 - URLSession:dataTask:didReceiveResponse:completionHandler:
 - URLSession:dataTask:didBecomeDownloadTask:
 - URLSession:dataTask:didReceiveData:
 - URLSession:dataTask:willCacheResponse:completionHandler:

NSURLSessionDownloadDelegate

 - URLSession:downloadTask:didFinishDownloadingToURL:
 - URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesWritten:totalBytesExpectedToWrite:
 - URLSession:downloadTask:didResumeAtOffset:expectedTotalBytes:

回調調用順序:

① URLSession:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:

由于使用的是POST方式,因此該回調是首先調用的。邏輯:找到task對應的delegate=>調用delegate的URLSession:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:來更新delegate維護的uploadProgress。若用戶自定義了taskDidSendBodyData block,則調用。

② URLSession:task:didFinishCollectingMetrics:

該方法調用時機不定,用來收集整個請求的信息。邏輯:找到task對應的delegate=>調用delegate的URLSession:task:didFinishCollectingMetrics:來為delegate.sessionTaskMetrics賦值。若用戶自定義了taskDidFinishCollectingMetrics block,則調用。

③ URLSession:dataTask:didReceiveData:
收到response data時執行。邏輯:找到task對應的delegate=>調用delegate的URLSession:task:didCompleteWithError:來更新delegate內部維護的downloadProgress并使用mutableData保存response data。若用戶自定義了dataTaskDidReceiveData block,則調用。

④ URLSession:task:didCompleteWithError:

這是最后執行的回調。邏輯:找到task對應的delegate=>調用delegate的URLSession:task:didCompleteWithError:,并刪除delegate和task的對應關系。若用戶自定義了taskDidComplete block,則調用。

數據的處理主要在delegate的URLSession:task:didCompleteWithError:中,主要做了以下幾件工作:

  • 構造userInfo字典:

    userInfo{
    AFNetworkingTaskDidCompleteResponseSerializerKey:<AFJSONResponseSerializer: 0x600003db6820>,
    AFNetworkingTaskDidCompleteSessionTaskMetrics:"...",
    AFNetworkingTaskDidCompleteResponseDataKey:"<length=24,bytes=0x567283d>"http://response data
    /*若出錯則會包含以下key*/
    AFNetworkingTaskDidCompleteErrorKey:"error" //error數據
    }
  • 若出錯,則直接調用completeHandler回調,之后將上面的userInfo以通知AFNetworkingTaskDidCompleteNotification的形式廣播出去。

  • 若未出錯,則使用responseSerializer(AFJSONResponseSerializer實例)將data轉換為NSDictionary。若轉換過程未出錯,則在userInfo中增加{AFNetworkingTaskDidCompleteSerializedResponseKey:responseObject};若出錯,則增加{AFNetworkingTaskDidCompleteErrorKey:error}。之后調用completeHandler回調,并將userInfo以通知AFNetworkingTaskDidCompleteNotification的形式廣播出去。

經過上面的處理,最終要么error要么responseObject會返回到POST的回調中去。整個網絡請求就完成了。

三、AFNetworking中的干貨

上面我們跟著一個簡單的POST請求了解了AFN的整個工作流程,但還有一些細節我覺得還是值得我們學習的。

  1. 如果response不是標準格式的JSON數據或者我們需要對原始data進行加密解密操作該如何做?

    答:將responseSerializer設置為AFHTTPResponseSerializer的實例,這樣response data 會以原始data的形式返回給上層。AFHTTPResponseSerializer僅針對code和contenttype進行校驗,而默認的AFJSONResponseSerializer除了校驗code和contenttype之外還對JSON格式進行校驗,并直接解析成NSDictionary。當然,如果想對response data想要做最全面的自定義處理,最直接的方式當然是自定義AFHTTPResponseSerializer的子類,并重寫協議方法

    - (id)responseObjectForResponse:(NSHTTPURLResponse *)response
                              data:(NSData *)data
                             error:(NSError *__autoreleasing *)error
  2. 如果返回的JSON數據中包含NSNull數據該如何處理?

    答:將responseSerializer(AFJSONResponseSerializer實例)的removesKeysWithNullValues屬性設置為YES。這樣一來在JSON轉換為NSDictionary之后會將NSDictionary中的NSNull值去除。

  3. 如果我想自己更改請求參數的格式(即不用默認的name=layne&age=30這種)該如何設置?

    答:調用requestSerializer的-setQueryStringSerializationWithBlock:設置自定義的格式。在requestSerializer處理參數的時候會先去判斷queryStringSerialization block是否為空,若不為空,則使用該block處理參數。若為空,則使用默認的格式(如name=layne&age=30)生成參數串。

  4. 除了使用GET/POST方法的回調,還可以通過什么方式獲得網絡請求結果?

    答:還可以使用notification。AFN中有一個名為AFNetworkingTaskDidCompleteNotification的通知,可以通過監聽該通知獲取網絡請求結果。AFURLSessionManagerTaskDelegate的URLSession:task:didCompleteWithError:中在調用completeHandler之后會發送通知:

    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];//將task和userInfo一起廣播出去
                   });
               });
  5. 如何更改回調所在的線程?

    答:指定manager的completeQueue。這樣回調就會在其他線程中執行。

    _manager.completionQueue = dispatch_queue_create("myqueue", DISPATCH_QUEUE_SERIAL);
  6. AFURLSessionManager如何獲取NSURLSession維護的task?

    答:使用getTasksWithCompletionHandler:API并使用信號量保證線程安全。

    - (NSArray *)tasksForKeyPath:(NSString *)keyPath { //tasks、uploadTask、downloadTasks
       __block NSArray *tasks = nil;
       dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
       [self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
           if ([keyPath isEqualToString:NSStringFromSelector(@selector(dataTasks))]) {
               tasks = dataTasks;
           } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(uploadTasks))]) {
               tasks = uploadTasks;
           } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(downloadTasks))]) {
               tasks = downloadTasks;
           } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(tasks))]) {
               tasks = [@[dataTasks, uploadTasks, downloadTasks] valueForKeyPath:@"@unionOfArrays.self"];
           }
    
           dispatch_semaphore_signal(semaphore);
       }];
    
       dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    
       return tasks;
    }
  7. 若我想取消一個剛發起的網絡請求該如何做?

    答:AFHTTPSessionManager的POST/GET方法返回的是task對象,直接[task cancel]即可。如:

    NSURLSessionDataTask *task = [self.manager POST:@"http://www.layne.com" 
               parameters:@{@"name":@"layne",@"age":@30} 
                  headers:@{@"TestName":@"myTest"} 
                 progress:nil 
            success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
                    NSLog(@"success:%@",responseObject);
            } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
                    NSLog(@"fail:%@",error);
       }];
    [task cancel];//直接cancel
  8. 若我想取消所有task該如何做?

    答:使用如下方法。建議resetSession傳入YES將session重置,否則下次網絡請求會crash,并提示:“Attempted to create a task in a session that has been invalidated

    - (void)invalidateSessionCancelingTasks:(BOOL)cancelPendingTasks resetSession:(BOOL)resetSession {
       if (cancelPendingTasks) {
           [self.session invalidateAndCancel];
       } else {
           [self.session finishTasksAndInvalidate];
       }
       if (resetSession) {
           self.session = nil;
       }
    }
    例如:
    [self.manager invalidateSessionCancelingTasks:YES resetSession:YES];

至此AFNetworking是如何工作的我們就知道了。看了源碼之后不得不感嘆作者真是神人,不僅優雅的給出了NSURLSession的使用范例,而且還包含了業務層面的巧妙設計。嗯,閱讀源碼使我快樂^_^。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

洛扎县| 江达县| 石河子市| 嵊泗县| 井研县| 株洲市| 甘洛县| 乌兰浩特市| 伊通| 依兰县| 兴义市| 麻城市| 宁武县| 洛川县| 五大连池市| 永年县| 大冶市| 陇西县| 青阳县| 邹城市| 肇东市| 舒城县| 佳木斯市| 友谊县| 富锦市| 赣州市| 鹰潭市| 政和县| 永嘉县| 临西县| 通江县| 邵阳县| 鹤岗市| 遵化市| 延安市| 民权县| 祁门县| 达日县| 南乐县| 六枝特区| 张家口市|