YTKNetwork 原始碼閱讀(二)-一次POST請求的完整歷程

weixin_33866037發表於2017-03-11

寫在前面:

  1. 這篇文寫了什麼?
    用YTKNetwork走一次網路請求的完整歷程。

  2. 我能看到哪些內容?
    以下是YTKNetwork官方文件的原話,說的是相比 AFNetworking,YTKNetwork 提供了以下更高階的功能,其中,後面打對號的部分是在這個歷程中能看到的風景。

    • 支援按時間快取網路請求內容 ✅
    • 支援按版本號快取網路請求內容 ✅
    • 支援統一設定伺服器和 CDN 的地址 ✅
    • 支援檢查返回 JSON 內容的合法性 ✅
    • 支援檔案的斷點續傳
    • 支援 block 和 delegate 兩種模式的回撥方式 ✅
    • 支援批量的網路請求傳送,並統一設定它們的回撥(實現在 YTKBatchRequest 類中)
    • 支援方便地設定有相互依賴的網路請求的傳送,例如:傳送請求 A,根據請求 A 的結果,選擇性的傳送請求 B 和 C,再根據 B 和 C 的結果,選擇性的傳送請求 D。(實現在 YTKChainRequest 類中)
    • 支援網路請求 URL 的 filter,可以統一為網路請求加上一些引數,或者修改一些路徑。 ✅
    • 定義了一套外掛機制,可以很方便地為 YTKNetwork 增加功能。猿題庫官方現在提供了一個外掛,可以在某些網路請求發起時,在介面上顯示“正在載入”的 HUD。

旅程前的準備:(這一部分是YTKNetwork的基礎用法,可以略過不看)

  1. 設定baseUrl
    //支援統一設定伺服器和 CDN 的地址 ✅
    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    YTKNetworkConfig *config = [YTKNetworkConfig sharedConfig];
    config.baseUrl = @"http://yuantiku.com";
    }

  2. 建立請求類

     // UserInfoRequest.m
     @interface UserInfoRequest () {
         NSString *_uid;
     }
     @end
    
     @implementation UserInfoRequest
    
     - (instancetype)initWithUid:(NSString *)uid {
         self = [super init];
         if (self) {
             _uid = uid;
         }
         return self;
     }
    
     - (NSString *)requestUrl {
         return @"/OwoccAppAPI/api/user";
     }
    
     - (YTKRequestMethod)requestMethod {
         return YTKRequestMethodPost;
     }
    
     - (id)requestArgument {
         return @{
                 @"uid": _uid,
                 };
     }
    

啟程

  1. 開始一個新請求

     NSString *uid = @"1002";
     //1. 建立一個請求例項,uid是引數。這個請求是post。
     UserInfoRequest *userInfoReq = [[UserInfoRequest alloc] initWithUid:uid];
     //2. 便利請求方法,選擇了block回撥的方式。點進這個方法,去看看它的實現。
     [userInfoReq startWithCompletionBlockWithSuccess:^(YTKBaseRequest *request) {
         NSLog(@"請求成功,返回資料:%@",request.responseString);
     } failure:^(YTKBaseRequest *request) {
         NSLog(@"請求失敗");
     }];
    
  2. 請求便利方法

     - (void)startWithCompletionBlockWithSuccess:(void (^)(YTKBaseRequest *request))success
                                         failure:(void (^)(YTKBaseRequest *request))failure {
         //1. 設定好成功和失敗的回撥。請看下                                   
         [self setCompletionBlockWithSuccess:success failure:failure];
         //2. 開始
         [self start];
     }
    
     - (void)setCompletionBlockWithSuccess:(void (^)(YTKBaseRequest *request))success
                                 failure:(void (^)(YTKBaseRequest *request))failure {
         self.successCompletionBlock = success;
         self.failureCompletionBlock = failure;
     }
    
  3. 開始請求,首先看能不能從快取拿資料

     - (void)start {
         //1. 如果忽略快取,直接開始請求。如果不忽略,往下 走
         if (self.ignoreCache) {
             [super start];
             return;
         }
    
         //2. 支援按時間快取網路請求內容 ✅。如果沒有設定cacheTimeInSeconds,直接開始請求。如果設定了,往下 走
         if ([self cacheTimeInSeconds] < 0) {
             [super start];
             return;
         }
    
         //3. 支援按版本號快取網路請求內容✅。
         long long cacheVersionFileContent = [self cacheVersionFileContent]; //已經快取內容的版本號
         //如果cacheVersionFileContent和這個請求的版本號不同的話,直接開始請求。如果相同,往下 走
         if (cacheVersionFileContent != [self cacheVersion]) {
             [super start];
             return;
         }
    
         //4. 檢視快取是否存在。如果不存在,直接開始請求。如果存在,往下 走。快取在沙盒中的路徑是: /Library/LazyRequestCache/POST+完整連結地址+引數+應用版本號+敏感內容
         NSString *path = [self cacheFilePath];
         NSFileManager *fileManager = [NSFileManager defaultManager];
         if (![fileManager fileExistsAtPath:path isDirectory:nil]) {
             [super start];
             return;
         }
    
         //5. 檢查快取檔案最後一次修改時間到現在的間隔。如果間隔大於我們設定的cacheTimeInSeconds直接開始請求。如果小於,往下 走
         int seconds = [self cacheFileDuration:path];
         if (seconds < 0 || seconds > [self cacheTimeInSeconds]) {
             [super start];
             return;
         }
    
         //6. 從快取中拿資料。如果為nil,直接開始請求。如果不為nil,往下 走
         _cacheJson = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
         if (_cacheJson == nil) {
             [super start];
             return;
         }
    
         //7. 如果走到這裡了,說明沒有走介面,直接從快取拿的資料。
          _dataFromCache = YES;//第一步
         [self requestCompleteFilter];//第二步
         //第三步:走請求完成的回撥。支援 block 和 delegate 兩種模式的回撥方式 ✅
         YTKRequest *strongSelf = self;
         [strongSelf.delegate requestFinished:strongSelf];
         if (strongSelf.successCompletionBlock) {
             strongSelf.successCompletionBlock(strongSelf);
         }
         [strongSelf clearCompletionBlock];//把成功和失敗的block置為nil
     }
    
  4. 請求真正開始(沒能從快取拿資料)

     /// append self to request queue
     - (void)start {
         [self toggleAccessoriesWillStartCallBack];//不用看
         //command 設計模式:把request傳給接收器(這裡就是YTKNetworkAgent)
         [[YTKNetworkAgent sharedInstance] addRequest:self];
     }
    
  5. YTKNetworkAgent的addRequest:方法

     - (void)addRequest:(YTKBaseRequest *)request {
         //1. 獲取請求方法,這裡是POST
         YTKRequestMethod method = [request requestMethod];
         //2. 組合完整連結地址: baseUrl + requestUrl
         //支援網路請求 URL 的 filter,可以統一為網路請求加上一些引數,或者修改一些路徑。✅
         NSString *url = [self buildRequestUrl:request];
         //3. 請求引數
         id param = request.requestArgument;
         //4.
         AFConstructingBlock constructingBlock = [request constructingBodyBlock];
         //5. 請求序列化型別
         if (request.requestSerializerType == YTKRequestSerializerTypeHTTP) {
             _manager.requestSerializer = [AFHTTPRequestSerializer serializer];
         } else if (request.requestSerializerType == YTKRequestSerializerTypeJSON) {
             _manager.requestSerializer = [AFJSONRequestSerializer serializer];
         }
         //6. 請求超時時間。可以自定義,預設60s
         _manager.requestSerializer.timeoutInterval = [request requestTimeoutInterval];
    
         //7. 如果需要的話,設定訪問伺服器的username和password
         NSArray *authorizationHeaderFieldArray = [request requestAuthorizationHeaderFieldArray];
         if (authorizationHeaderFieldArray != nil) {
             [_manager.requestSerializer setAuthorizationHeaderFieldWithUsername:(NSString *)authorizationHeaderFieldArray.firstObject
                                                                     password:(NSString *)authorizationHeaderFieldArray.lastObject];
         }
         
         //8. 如果需要的話,配置一些請求頭的資訊
         NSDictionary *headerFieldValueDictionary = [request requestHeaderFieldValueDictionary];
         if (headerFieldValueDictionary != nil) {
             for (id httpHeaderField in headerFieldValueDictionary.allKeys) {
                 id value = headerFieldValueDictionary[httpHeaderField];
                 if ([httpHeaderField isKindOfClass:[NSString class]] && [value isKindOfClass:[NSString class]]) {
                     [_manager.requestSerializer setValue:(NSString *)value forHTTPHeaderField:(NSString *)httpHeaderField];
                 } else {
                     YTKLog(@"Error, class of key/value in headerFieldValueDictionary should be NSString.");
                 }
             }
         }
    
         //9. 自定義url。
         NSURLRequest *customUrlRequest= [request buildCustomUrlRequest];
         if (customUrlRequest) {
             //注意: 如果自定義了url,會忽略請求類的requestUrl, requestArgument, requestMethod, requestSerializerType
             AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:customUrlRequest];
             [operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
                 [self handleRequestResult:operation];
             } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
                 [self handleRequestResult:operation];
             }];
             request.requestOperation = operation;
             operation.responseSerializer = _manager.responseSerializer;
             [_manager.operationQueue addOperation:operation];
         } else {
             //GET方法,我們這裡的示例是POST,看下
             if (method == YTKRequestMethodGet) {
                 if (request.resumableDownloadPath) {
                     // add parameters to URL;
                     NSString *filteredUrl = [YTKNetworkPrivate urlStringWithOriginUrlString:url appendParameters:param];
    
                     NSURLRequest *requestUrl = [NSURLRequest requestWithURL:[NSURL URLWithString:filteredUrl]];
                     AFDownloadRequestOperation *operation = [[AFDownloadRequestOperation alloc] initWithRequest:requestUrl targetPath:request.resumableDownloadPath shouldResume:YES];
                     [operation setProgressiveDownloadProgressBlock:request.resumableDownloadProgressBlock];
                     [operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
                         [self handleRequestResult:operation];
                     }                                failure:^(AFHTTPRequestOperation *operation, NSError *error) {
                         [self handleRequestResult:operation];
                     }];
                     request.requestOperation = operation;
                     [_manager.operationQueue addOperation:operation];
                 } else {
                     request.requestOperation = [_manager GET:url parameters:param success:^(AFHTTPRequestOperation *operation, id responseObject) {
                         [self handleRequestResult:operation];
                     }                                failure:^(AFHTTPRequestOperation *operation, NSError *error) {
                         [self handleRequestResult:operation];
                     }];
                 }
             } else if (method == YTKRequestMethodPost) {
                 //10. 開始請求,用的是AFNetworking
                 if (constructingBlock != nil) {
                     request.requestOperation = [_manager POST:url parameters:param constructingBodyWithBlock:constructingBlock
                                                     success:^(AFHTTPRequestOperation *operation, id responseObject) {
                                                         [self handleRequestResult:operation];
                                                     } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
                                 [self handleRequestResult:operation];
                             }];
                 } else {
                     request.requestOperation = [_manager POST:url parameters:param success:^(AFHTTPRequestOperation *operation, id responseObject) {
                         [self handleRequestResult:operation];
                     }                                 failure:^(AFHTTPRequestOperation *operation, NSError *error) {
                         //12. 請求完成,處理請求的結果.(11請看下面)
                         [self handleRequestResult:operation];
                     }];
                 }
             } else if (method == YTKRequestMethodHead) {
                 request.requestOperation = [_manager HEAD:url parameters:param success:^(AFHTTPRequestOperation *operation) {
                     [self handleRequestResult:operation];
                 }                                 failure:^(AFHTTPRequestOperation *operation, NSError *error) {
                     [self handleRequestResult:operation];
                 }];
             } else if (method == YTKRequestMethodPut) {
                 request.requestOperation = [_manager PUT:url parameters:param success:^(AFHTTPRequestOperation *operation, id responseObject) {
                     [self handleRequestResult:operation];
                 }                                failure:^(AFHTTPRequestOperation *operation, NSError *error) {
                     [self handleRequestResult:operation];
                 }];
             } else if (method == YTKRequestMethodDelete) {
                 request.requestOperation = [_manager DELETE:url parameters:param success:^(AFHTTPRequestOperation *operation, id responseObject) {
                     [self handleRequestResult:operation];
                 }                                   failure:^(AFHTTPRequestOperation *operation, NSError *error) {
                     [self handleRequestResult:operation];
                 }];
             } else if (method == YTKRequestMethodPatch) {
                 request.requestOperation = [_manager PATCH:url parameters:param success:^(AFHTTPRequestOperation *operation, id responseObject) {
                     [self handleRequestResult:operation];
                 } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
                     [self handleRequestResult:operation];
                 }];
             } else {
                 YTKLog(@"Error, unsupport method type");
                 return;
             }
         }
    
         YTKLog(@"Add request: %@", NSStringFromClass([request class]));
         //11. 把request存到_requestsRecord裡面
         [self addOperation:request];
     }
    
  6. 處理請求的下來的資料handleRequestResult:

     - (void)handleRequestResult:(AFHTTPRequestOperation *)operation {
         NSString *key = [self requestHashKey:operation];
         //1. 把request從_requestsRecord拿出來
         YTKBaseRequest *request = _requestsRecord[key];
         YTKLog(@"Finished Request: %@", NSStringFromClass([request class]));
         //2. 如果request存在,接著往下走
         if (request) {
             // 支援檢查返回 JSON 內容的合法性 ✅
             BOOL succeed = [self checkResult:request];
             if (succeed) {
                 //成功,走成功的回撥
                 [request toggleAccessoriesWillStopCallBack];
                 [request requestCompleteFilter];
                 if (request.delegate != nil) {
                     [request.delegate requestFinished:request];
                 }
                 if (request.successCompletionBlock) {
                     request.successCompletionBlock(request);
                 }
                 [request toggleAccessoriesDidStopCallBack];
             } else {
                 //失敗,走失敗的回撥
                 YTKLog(@"Request %@ failed, status code = %ld",
                         NSStringFromClass([request class]), (long)request.responseStatusCode);
                 [request toggleAccessoriesWillStopCallBack];
                 [request requestFailedFilter];
                 if (request.delegate != nil) {
                     [request.delegate requestFailed:request];
                 }
                 if (request.failureCompletionBlock) {
                     request.failureCompletionBlock(request);
                 }
                 [request toggleAccessoriesDidStopCallBack];
             }
         }
         //3. 把request從requestRecord移除出去
         [self removeOperation:operation];
         //4. 把成功和失敗的block回撥置為nil
         [request clearCompletionBlock];
     }
    

YTKNetwork 原始碼閱讀(一)-整體框架

相關文章