概述
AFNetwokring目前是3.x版本,基于NSURLSession的功能進(jìn)行封裝,而2.x版本是基于NSURLConnection。由于NSURLConnection逐漸被NSURLSession所取代,2.x版本逐漸被3.x取代。本篇分析一下2.x版本,因?yàn)樵摪姹旧婕暗囊恍┐a值得學(xué)習(xí),例如NSOperation、KVO的使用。
AFHTTPRequestOperationManager
AFHTTPRequestOperationManager是AFN封裝的管理HTTP請求的類,首先初始化方法中設(shè)置了一些參數(shù)值,代碼注釋如下:
- (instancetype)initWithBaseURL:(NSURL *)url {
...
self.baseURL = url;
self.requestSerializer = [AFHTTPRequestSerializer serializer]; //序列化
self.responseSerializer = [AFJSONResponseSerializer serializer];//反序列化
self.securityPolicy = [AFSecurityPolicy defaultPolicy]; //默認(rèn)的安全策略
self.reachabilityManager = [AFNetworkReachabilityManager sharedManager];//監(jiān)聽網(wǎng)絡(luò)狀態(tài)
self.operationQueue = [[NSOperationQueue alloc] init]; //任務(wù)隊(duì)列
self.shouldUseCredentialStorage = YES;
return self;
}
初始化方法設(shè)置了請求報(bào)文序列化/反序列化對象,以及默認(rèn)的安全策略,網(wǎng)絡(luò)監(jiān)聽對象,任務(wù)隊(duì)列。
AFHTTPRequestOperationManager提供了一系列HTTP請求相關(guān)的方法,例如GET、POST、PATCH等,內(nèi)部實(shí)現(xiàn)相同,只是method參數(shù)值不同,以GET請求方法為例,代碼注釋如下:
- (AFHTTPRequestOperation *)GET:(NSString *)URLString
parameters:(id)parameters
success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure
{
AFHTTPRequestOperation *operation = [self HTTPRequestOperationWithHTTPMethod:@"GET" URLString:URLString parameters:parameters success:success failure:failure];
[self.operationQueue addOperation:operation];
return operation;
}
首先創(chuàng)建一個AFHTTPRequestOperation類型的NSOperation對象,然后將NSOperation對象加入operationQueue隊(duì)列中,開始執(zhí)行operation。在創(chuàng)建AFHTTPRequestOperation對象的方法中,首先通過requestSerializer構(gòu)建NSURLRequest對象,然后設(shè)置相關(guān)屬性,設(shè)置completionBlock,代碼注釋如下:
- (AFHTTPRequestOperation *)HTTPRequestOperationWithRequest:(NSURLRequest *)request
success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure
{
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
... //設(shè)置相關(guān)屬性
[operation setCompletionBlockWithSuccess:success failure:failure]; //設(shè)置operation結(jié)束時的completionBlock
operation.completionQueue = self.completionQueue; //執(zhí)行completionBlock的隊(duì)列
operation.completionGroup = self.completionGroup; //執(zhí)行completionBlock的group
return operation;
}
AFHTTPRequestOperation繼承AFURLConnectionOperation,AFURLConnectionOperation真正負(fù)責(zé)網(wǎng)絡(luò)請求的發(fā)出以及處理,AFHTTPRequestOperation設(shè)置completionBlock的方法如下:
- (void)setCompletionBlockWithSuccess:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-retain-cycles"
#pragma clang diagnostic ignored "-Wgnu"
self.completionBlock = ^{
if (self.completionGroup) {
dispatch_group_enter(self.completionGroup);
}
//在異步隊(duì)列中執(zhí)行
dispatch_async(http_request_operation_processing_queue(), ^{
if (self.error) {
if (failure) { //網(wǎng)絡(luò)請求失敗
//在completionQueue或者主線程隊(duì)列中執(zhí)行失敗block
dispatch_group_async(self.completionGroup ?: http_request_operation_completion_group(), self.completionQueue ?: dispatch_get_main_queue(), ^{
failure(self, self.error);
});
}
} else { //網(wǎng)絡(luò)請求成功
//反序列化報(bào)文數(shù)據(jù)
id responseObject = self.responseObject;
if (self.error) {
if (failure) {
//反序列化報(bào)文數(shù)據(jù)失敗,在completionQueue或者主線程隊(duì)列中執(zhí)行失敗block
dispatch_group_async(self.completionGroup ?: http_request_operation_completion_group(), self.completionQueue ?: dispatch_get_main_queue(), ^{
failure(self, self.error);
});
}
} else {
if (success) {
//反序列化報(bào)文數(shù)據(jù)成功,在completionQueue或者主線程隊(duì)列中執(zhí)行成功block
dispatch_group_async(self.completionGroup ?: http_request_operation_completion_group(), self.completionQueue ?: dispatch_get_main_queue(), ^{
success(self, responseObject);
});
}
}
}
if (self.completionGroup) {
dispatch_group_leave(self.completionGroup);
}
});
};
#pragma clang diagnostic pop
}
該方法定義一個void (^)(void)類型的block,設(shè)置給父類的completionBlock屬性,當(dāng)執(zhí)行completionBlock時,首先會判斷網(wǎng)絡(luò)請求是否錯誤,如果出錯,直接在completionQueue或者主線程中調(diào)用failure的block拋給調(diào)用層。如果網(wǎng)絡(luò)請求成功,則調(diào)用responseObject方法反序列化響應(yīng)報(bào)文數(shù)據(jù)responseData,代碼注釋如下:
- (id)responseObject {
[self.lock lock];
if (!_responseObject && [self isFinished] && !self.error) {
NSError *error = nil;
//反序列化響應(yīng)報(bào)文數(shù)據(jù)
self.responseObject = [self.responseSerializer responseObjectForResponse:self.response data:self.responseData error:&error];
if (error) {
self.responseSerializationError = error;
}
}
[self.lock unlock];
return _responseObject;
}
由于該方法在http_request_operation_processing_queue()中執(zhí)行,不影響主線程的性能。如果反序列化成功,調(diào)用success的blcok將error拋給調(diào)用層,如果失敗,調(diào)用failure的block將反序列化后的對象拋給調(diào)用層。
AFURLConnectionOperation
AFURLConnectionOperation負(fù)責(zé)發(fā)送網(wǎng)絡(luò)請求,處理delegate回調(diào)方法。AFURLConnectionOperation繼承NSOperation,眾所周知,當(dāng)實(shí)現(xiàn)一個自定義的NSOperation時,需要重寫NSOperation的相關(guān)方法,以確保operation機(jī)制的正常運(yùn)行。
初始化方法
初始化方法設(shè)置了相關(guān)參數(shù),代碼注釋如下:
- (instancetype)initWithRequest:(NSURLRequest *)urlRequest {
NSParameterAssert(urlRequest);
self = [super init];
if (!self) {
return nil;
}
_state = AFOperationReadyState; //狀態(tài)設(shè)置為準(zhǔn)備執(zhí)行
self.lock = [[NSRecursiveLock alloc] init]; //創(chuàng)建遞歸鎖
self.lock.name = kAFNetworkingLockName;
self.runLoopModes = [NSSet setWithObject:NSRunLoopCommonModes];
self.request = urlRequest; //設(shè)置urlRequest
self.shouldUseCredentialStorage = YES;
self.securityPolicy = [AFSecurityPolicy defaultPolicy]; //設(shè)置securityPolicy
return self;
}
該方法設(shè)置用于網(wǎng)絡(luò)請求的request,初始化了遞歸鎖,安全策略對象securityPolicy,同時設(shè)置了狀態(tài)為AFOperationReadyState(準(zhǔn)備執(zhí)行)。
狀態(tài)機(jī)制
當(dāng)operation加入到operationQueue中時,operationQueue會通過KVO的方式監(jiān)聽operation的狀態(tài),NSOperation有幾種狀態(tài),分別對應(yīng)以下屬性:
isReady(是否準(zhǔn)備執(zhí)行)
isExecuting(是否正在執(zhí)行)
isCancelled(是否取消)
isPaused(是否暫停)
isFinished(是否完成)
上面的屬性狀態(tài)決定operation的生命周期,operationQueue監(jiān)聽operation的狀態(tài)屬性,當(dāng)operation的isFinished屬性為YES時,說明operation生命周期結(jié)束,operation會在隊(duì)列中被釋放。AFURLConnectionOperation實(shí)現(xiàn)了自定義的operation,重寫了以下屬性方法:
- (BOOL)isReady {
return self.state == AFOperationReadyState && [super isReady];
}
- (BOOL)isExecuting {
return self.state == AFOperationExecutingState;
}
- (BOOL)isFinished {
return self.state == AFOperationFinishedState;
}
通過getter方法訪問operation的屬性時,返回的狀態(tài)值會根據(jù)AFNetworking維護(hù)的枚舉值來確定。下面是枚舉類型AFOperationState的代碼:
typedef NS_ENUM(NSInteger, AFOperationState) {
AFOperationPausedState = -1, //暫停
AFOperationReadyState = 1, //準(zhǔn)備執(zhí)行
AFOperationExecutingState = 2, //正在執(zhí)行
AFOperationFinishedState = 3, //完成
};
分別對應(yīng)operation的狀態(tài)屬性,同時實(shí)現(xiàn)-setState:方法來更新AFOperationState的枚舉值,下面是代碼注釋:
- (void)setState:(AFOperationState)state {
if (!AFStateTransitionIsValid(self.state, state, [self isCancelled])) {
return;
}
[self.lock lock];
NSString *oldStateKey = AFKeyPathFromOperationState(self.state); //原狀態(tài)
NSString *newStateKey = AFKeyPathFromOperationState(state); //新狀態(tài)
[self willChangeValueForKey:newStateKey]; //手動發(fā)送KVO通知
[self willChangeValueForKey:oldStateKey]; //手動發(fā)送KVO通知
_state = state; //切換狀態(tài)
[self didChangeValueForKey:oldStateKey]; //手動發(fā)送KVO通知
[self didChangeValueForKey:newStateKey]; //手動發(fā)送KVO通知
[self.lock unlock];
}
首先通過AFKeyPathFromOperationState方法將新舊AFOperationState枚舉值映射成operation的狀態(tài)屬性名,然后更新AFOperationState枚舉值,當(dāng)外界訪問operation的狀態(tài)屬性時,狀態(tài)已經(jīng)改變。同時手動發(fā)送KVO通知,通知operationQueue,operation的狀態(tài)屬性發(fā)生了改變。
start方法
如果實(shí)現(xiàn)自定義的NSOperation,需要重寫start方法,下面是代碼注釋:
- (void)start {
[self.lock lock];
if ([self isCancelled]) { //如果operation之前被取消,調(diào)用cancelConnection方法取消connection
[self performSelector:@selector(cancelConnection) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
} else if ([self isReady]) { //如果operation準(zhǔn)備執(zhí)行,調(diào)用operationDidStart方法開始構(gòu)建connection,發(fā)送網(wǎng)絡(luò)請求
self.state = AFOperationExecutingState;
[self performSelector:@selector(operationDidStart) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
}
[self.lock unlock];
}
該方法首先判斷operation當(dāng)前的狀態(tài),如果之前已經(jīng)被取消了,則調(diào)用cancelConnection方法進(jìn)一步處理,該方法放在后文分析。如果新建operation,在初始化方法中,設(shè)置初始狀態(tài)是AFOperationReadyState,即operation的狀態(tài)isReady=YES,調(diào)用operationDidStart方法開始進(jìn)行網(wǎng)絡(luò)請求。同時AFNetworking創(chuàng)建了一個常駐線程來執(zhí)行connection相關(guān)的方法。常駐線程由類方法networkRequestThread創(chuàng)建,代碼如下:
+ (NSThread *)networkRequestThread {
static NSThread *_networkRequestThread = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil]; //創(chuàng)建常駐線程
[_networkRequestThread start];
});
return _networkRequestThread;
}
+ (void)networkRequestThreadEntryPoint:(id)__unused object {
@autoreleasepool {
[[NSThread currentThread] setName:@"AFNetworking"];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode]; //為runloop添加port,使runloop永不退出
[runLoop run];
}
}
首先在dispatch_once()中創(chuàng)建一個線程,由于dispatch_once中的block只會執(zhí)行一次,所以線程只會創(chuàng)建一次,且_networkRequestThread是static類型的,所以會常駐內(nèi)存不被釋放。每次調(diào)用networkRequestThread方法都會返回該線程指針。由于是手動創(chuàng)建的子線程,需要手動開啟它的runloop,并且在runloop中添加port,使其永不退出。我們將這個常駐線程稱為AFN線程。在AFN線程中執(zhí)行operationDidStart方法,下面是代碼注釋:
- (void)operationDidStart {
[self.lock lock];
if (![self isCancelled]) {
self.connection = [[NSURLConnection alloc] initWithRequest:self.request delegate:self startImmediately:NO];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
for (NSString *runLoopMode in self.runLoopModes) {
[self.connection scheduleInRunLoop:runLoop forMode:runLoopMode];
[self.outputStream scheduleInRunLoop:runLoop forMode:runLoopMode];
}
[self.outputStream open];
[self.connection start];
}
[self.lock unlock];
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingOperationDidStartNotification object:self];
});
}
首先創(chuàng)建了connection,設(shè)置當(dāng)前operation對象為connection對象的delegate,處理網(wǎng)絡(luò)請求回調(diào)的各個方法。如果startImmediately參數(shù)為YES,connection會立刻啟動,開始下載數(shù)據(jù),且connection在當(dāng)前線程的runloop中執(zhí)行,如果為NO,暫不開始請求數(shù)據(jù),需要調(diào)用start方法手動開始,同時調(diào)用scheduleInRunLoop:forMode:方法把NSURLConnection加入到指定線程的run loop中去運(yùn)行,否則會加入當(dāng)前線程的runloop中去,使用outputStream來接收網(wǎng)絡(luò)請求回來的數(shù)據(jù)。
NSURLConnectionDelegate
在connection網(wǎng)絡(luò)請求的過程中,將回調(diào)方法拋給delegate執(zhí)行,下面分析一下主要方法:
-
-(void)connection:willSendRequestForAuthenticationChallenge:方法
當(dāng)客戶端發(fā)送HTTPS請求給服務(wù)端時,會進(jìn)行SSL握手,在握手的過程中,服務(wù)端需要客戶端進(jìn)行授權(quán)的響應(yīng),客戶端對服務(wù)端發(fā)來的信息進(jìn)行校驗(yàn),在iOS代碼中,抽象為系統(tǒng)拋出delegate方法給上層代碼,同時傳入一個challenge對象,封裝了需要驗(yàn)證的信息,下面是代碼部分注釋:
- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge { ... if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) { //對serverTrust對象和host進(jìn)行校驗(yàn) if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) { //校驗(yàn)通過,生成一個憑證對象credential NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]; //使用credential給系統(tǒng) [[challenge sender] useCredential:credential forAuthenticationChallenge:challenge]; } else { //驗(yàn)證失敗,取消后續(xù)SSL連接 [[challenge sender] cancelAuthenticationChallenge:challenge]; } } else { if ([challenge previousFailureCount] == 0) { if (self.credential) { //直接用現(xiàn)有的credential給系統(tǒng) [[challenge sender] useCredential:self.credential forAuthenticationChallenge:challenge]; } else { [[challenge sender] continueWithoutCredentialForAuthenticationChallenge:challenge]; } } else { [[challenge sender] continueWithoutCredentialForAuthenticationChallenge:challenge]; } } }
challenge中的屬性protectionSpace是一個NSURLProtectionSpace對象,包含服務(wù)器的host、port、isProxy等信息,同時包含authenticationMethod,即校驗(yàn)方式的類型,如果類型是NSURLAuthenticationMethodServerTrust,則驗(yàn)證信任對象serverTrust,如果驗(yàn)證通過,生成一個憑證對象credential返回給服務(wù)端,具體的流程可以參考騰訊Bugly的文章。
-
-(void)connection: didReceiveData:
當(dāng)網(wǎng)絡(luò)連接建立后,服務(wù)器開始向客戶端傳輸數(shù)據(jù),系統(tǒng)會回調(diào)該方法,上層代碼負(fù)責(zé)接收并且拼裝response數(shù)據(jù)。下面是部分代碼注釋:
- (void)connection:(NSURLConnection __unused *)connection didReceiveData:(NSData *)data { NSUInteger length = [data length]; //需要讀取的字節(jié)長度 while (YES) { NSInteger totalNumberOfBytesWritten = 0; //本次一共寫入的字節(jié)長度 if ([self.outputStream hasSpaceAvailable]) { //outputStream有空間寫入 const uint8_t *dataBuffer = (uint8_t *)[data bytes]; NSInteger numberOfBytesWritten = 0; while (totalNumberOfBytesWritten < (NSInteger)length) { numberOfBytesWritten = [self.outputStream write:&dataBuffer[(NSUInteger)totalNumberOfBytesWritten] maxLength:(length - (NSUInteger)totalNumberOfBytesWritten)]; //將dataBuffer中的數(shù)據(jù)寫入outputStream中 if (numberOfBytesWritten == -1) { break; } totalNumberOfBytesWritten += numberOfBytesWritten; //累加寫入的字節(jié)長度 } break; } else { //outputStream沒有空間寫入 [self.connection cancel]; if (self.outputStream.streamError) { [self performSelector:@selector(connection:didFailWithError:) withObject:self.connection withObject:self.outputStream.streamError]; } return; } } ... }
該方法主要將data數(shù)據(jù)寫入outputStream中,對于本次數(shù)據(jù)data,如果一次性寫不全進(jìn)outputStream,則通過totalNumberOfBytesWritten記錄共寫入的字節(jié)長度,通過while循環(huán)控制,直到全部寫入。outputStream通過[NSOutputStream outputStreamToMemory]創(chuàng)建,是寫入內(nèi)存的流對象,如果hasSpaceAvailable返回NO,即后續(xù)返回的response數(shù)據(jù)沒有空間存放,則直接斷開網(wǎng)絡(luò)請求。
-
-(void)connectionDidFinishLoading:
當(dāng)網(wǎng)絡(luò)請求結(jié)束時,調(diào)用該方法,獲取最終的response數(shù)據(jù),結(jié)束本次operation。
- (void)connectionDidFinishLoading:(NSURLConnection __unused *)connection { self.responseData = [self.outputStream propertyForKey:NSStreamDataWrittenToMemoryStreamKey]; //獲取response數(shù)據(jù) [self.outputStream close]; //關(guān)閉outputStream if (self.responseData) { self.outputStream = nil; } self.connection = nil; [self finish]; //結(jié)束operation }
-
-(NSCachedURLResponse *)connection: willCacheResponse:
如果服務(wù)端需要將response數(shù)據(jù)緩存到客戶端的NSURLCache緩存系統(tǒng),在緩存到客戶端本地之前,會首先調(diào)用該方法,可以修改緩存的數(shù)據(jù),默認(rèn)是接口返回的response數(shù)據(jù)。
- (NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse *)cachedResponse { if (self.cacheResponse) { return self.cacheResponse(connection, cachedResponse); //修改服務(wù)端返回緩存數(shù)據(jù) } else { if ([self isCancelled]) { return nil; } return cachedResponse; } }
默認(rèn)網(wǎng)絡(luò)請求的緩存策略是UseProtocolCachePolicy,根據(jù)服務(wù)端返回的Cache-Control字段來開啟HTTP緩存功能,字段值可能包含 max-age,是公共 public 還是私有 private,或者不緩存no-cache 等信息。關(guān)于NSURLCache的相關(guān)講解,可以參考這篇文章。
- (NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse *)cachedResponse { if (self.cacheResponse) { return self.cacheResponse(connection, cachedResponse); //修改需要緩存的數(shù)據(jù) } else { if ([self isCancelled]) { return nil; } return cachedResponse; } }
控制生命周期
上文所述,AFURLConnectionOperation通過setState:方法實(shí)現(xiàn)了operation狀態(tài)的切換,從而控制operation的生命周期。下面分析一下,另外幾個方法:
-
finish方法
當(dāng)網(wǎng)絡(luò)請求結(jié)束時,該方法被調(diào)用,負(fù)責(zé)結(jié)束operation的生命周期:
- (void)finish { [self.lock lock]; self.state = AFOperationFinishedState; //設(shè)置結(jié)束狀態(tài),結(jié)束operation的生命周期 [self.lock unlock]; ... }
在setState方法里更改為AFOperationFinishedState狀態(tài)并且手動觸發(fā)KVO,通知operationQueue,isFinished屬性變化,觸發(fā)completionBlock,執(zhí)行block里面的代碼。
-
cancel方法
該方法取消一個cancel這個operation,同時調(diào)用cancelConnection方法取消當(dāng)前的connection連接。
- (void)cancel { [self.lock lock]; if (![self isFinished] && ![self isCancelled]) { [super cancel]; //調(diào)用NSOperation的cancel方法 if ([self isExecuting]) { [self performSelector:@selector(cancelConnection) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]]; } } [self.lock unlock]; }
注意cancelConnection方法也會在start方法中調(diào)用,新建一個請求時,當(dāng)發(fā)現(xiàn)這個operation之前被取消了,進(jìn)一步判斷connection是否建立,如果connection存在,先取消connection,然后調(diào)用finish方法,結(jié)束operation的生命周期。
-
pause方法和resume方法
AFURLConnectionOperation提供了pause和resume方法,pause方法將狀態(tài)改為AFOperationPausedState,同時取消當(dāng)前的connection。resume方法將狀態(tài)重新改為AFOperationReadyState,同時調(diào)用start方法,重新請求connection,將狀態(tài)改為AFOperationExecutingState。下面是代碼注釋:
- (void)pause { if ([self isPaused] || [self isFinished] || [self isCancelled]) { return; } [self.lock lock]; if ([self isExecuting]) { //取消當(dāng)前conneciton [self performSelector:@selector(operationDidPause) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]]; ... self.state = AFOperationPausedState; //暫停operation [self.lock unlock]; } - (void)resume { if (![self isPaused]) { return; } [self.lock lock]; self.state = AFOperationReadyState; //operation重置為isReady狀態(tài) [self start]; //新建connection,重新請求數(shù)據(jù),狀態(tài)職位isExecuting [self.lock unlock]; }
pause方法只是取消本次網(wǎng)絡(luò)請求,不會結(jié)束operation的生命周期,當(dāng)外界調(diào)用resume方法時,也只是重新進(jìn)行網(wǎng)絡(luò)請求。
小結(jié)
雖然NSURLConnection及其基礎(chǔ)上封裝的AF2.x版本逐漸被廢棄,但是作者關(guān)于operation的使用,以及如何實(shí)現(xiàn)一個網(wǎng)絡(luò)請求的處理流程,對于初學(xué)者來說,具有參考和學(xué)習(xí)的價值。