說到AFNetwokring這個(gè)強(qiáng)大第三方網(wǎng)絡(luò)請(qǐng)求庫疫稿,大家應(yīng)該都不陌生吧,ios開發(fā)片挂、mac開發(fā)都經(jīng)常用,主要是他使用起來簡單闪湾、方便昵时。下面我們看看他的源碼,來探討一下吧湾蔓。
首先瘫析,我們一起來看一下它的框架的組成部分吧。
上面的圖片我從AFNetworking的文件中接的圖默责,我們可以看出贬循,包含5個(gè)部分,其實(shí)AFHTTPSessionManager是AFURLSessionManager的子類桃序,所以說它的組成是四個(gè)部分:
網(wǎng)絡(luò)通信模塊(AFHTTPSessionmanager杖虾、AFURLSessionManager)
網(wǎng)絡(luò)狀態(tài)監(jiān)聽模塊(AFNetworkReachabilityManager)
網(wǎng)絡(luò)通信信息序列化反序列化策略模塊(AFURLRequestSErialization、AFURLResponseSerialization)
網(wǎng)絡(luò)通信安全策略模塊(AFSecurityPolicy)
對(duì)ios UIKit庫的拓展
其核心當(dāng)然就是網(wǎng)絡(luò)通信模塊AFURLSessionManager媒熊,這個(gè)類是對(duì)NSURLSession的進(jìn)一步的封裝
其他模塊均是配合網(wǎng)絡(luò)通信或?qū)σ延蠻IKIt的擴(kuò)展
一奇适、初始化方法
最終都會(huì)到- (instancetype)initWithBaseURL:(NSURL *)url sessionConfiguration:(NSURLSessionConfiguration *)configuration方法中來了。
這個(gè)方法主要做了什么事情呢芦鳍?
1嚷往、調(diào)父類的- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration 這個(gè)方法里面設(shè)置了
隊(duì)列為非主線程隊(duì)列,隊(duì)列的并發(fā)數(shù)為1柠衅,
初始化了session皮仁,設(shè)置了代理,
初始化了網(wǎng)絡(luò)通信監(jiān)聽菲宴、網(wǎng)絡(luò)安全策略
贷祈,初始化了保存網(wǎng)絡(luò)請(qǐng)求任務(wù)、對(duì)應(yīng)的任務(wù)代理到字典中?
遍歷session中的任務(wù)喝峦,將任務(wù)對(duì)應(yīng)的任務(wù)代理設(shè)為nil势誊,請(qǐng)求代理
2、設(shè)置默認(rèn)AFURLRequestSErialization愈犹、AFURLResponseSerialization键科,分別是請(qǐng)求序列對(duì)象和響應(yīng)序列對(duì)象,這個(gè)兩個(gè)東西會(huì)在NSURLRequest中設(shè)置請(qǐng)求頭漩怎、請(qǐng)求體等一些信息中用到
上面的分析勋颖,可以看到,主要的東西還在父類中做的勋锤,里面初始化方法里便利session饭玲,將session中任務(wù)的代理清空是一種防御性編程。
然后來看GET請(qǐng)求
- (NSURLSessionDataTask *)GET:(NSString *)URLString參數(shù):(ID)參數(shù)進(jìn)度:(void(^)(NSProgress * _Nonnull))downloadProgress成功:(無效(^)(NSURLSessionDataTask * _Nonnull叁执,id _Nullable))成功失斍牙濉:(void(^)(NSURLSessionDataTask * _Nullable矮冬,NSError * _Nonnull))失敗
{//生成一個(gè)任務(wù)
NSURLSessionDataTask * dataTask = [self dataTaskWithHTTPMethod:@“GET”URLString:URLString參數(shù):參數(shù)上傳進(jìn)度:無downloadProgress:downloadProgress成功:成功失敗:失敗];
//開始網(wǎng)絡(luò)請(qǐng)求[dataTask resume];返回dataTask;}
這里主要生成一個(gè)NSURLSessionDataTask來進(jìn)行網(wǎng)絡(luò)請(qǐng)求
我們繼續(xù)往父類里看次哈,看看這個(gè)方法到底做了什么:
- (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)方法URLString:(NSString *)URLString參數(shù):(ID)參數(shù)uploadProgress:(可為空(void)(^)(NSProgress * uploadProgress))uploadProgressdownloadProgress :(可空(void)(^)(NSProgress * downloadProgress))downloadProgress成功:(void(^)(NSURLSessionDataTask *胎署,id))成功失敗:(void(^)(NSURLSessionDataTask *窑滞,NSError *))失敗{NSError * serializationError = nil;
//把參數(shù)琼牧,還有各種東西轉(zhuǎn)化為一個(gè)請(qǐng)求
NSMutableURLRequest * request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];
If(serializationError){如果(失敗)
{#pragma clang診斷推送#pragma clang診斷忽略“-WgnU”//如果解析錯(cuò)誤哀卫,直接返回dispatch_async(self.completionQueue巨坊?:dispatch_get_main_queue(),^ {失敶烁摹(nil趾撵,serializationError);});
#pragma clang診斷流行}返回零;}__block NSURLSessionDataTask * dataTask = nil;dataTask = [self dataTaskWithRequest:request上傳進(jìn)度:上傳進(jìn)度downloadProgress:downloadProgresscompletionHandler:^(NSURLResponse * __unused response,id responseObject共啃,NSError * error){如果(錯(cuò)誤){如果(失斦嫉鳌){失敗(dataTask勋磕,錯(cuò)誤);}} else {如果(成功){成功(dataTask妈候,responseObject);}}}];返回dataTask;}
這個(gè)
這個(gè)方法做了兩件事:
1、用self.requestSerializer和各種參數(shù)去獲取一個(gè)最終網(wǎng)絡(luò)請(qǐng)求需要的NSMutableURLRequest
2挂滓、調(diào)用另外一個(gè)方法dataTaskWithRequest去拿到我們需要NSURLSessionDataTask,并且在回掉中調(diào)用成功或失敗的回掉
- (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)方法URLString:(NSString *)URLString參數(shù):(ID)參數(shù)uploadProgress:(可為空(void)(^)(NSProgress * uploadProgress))uploadProgressdownloadProgress :(可空(void)(^)(NSProgress * downloadProgress))downloadProgress成功:(void(^)(NSURLSessionDataTask *苦银,id))成功失敗:(void(^)(NSURLSessionDataTask *赶站,NSError *))失敗{NSError * serializationError = nil;//把參數(shù)幔虏,還有各種東西轉(zhuǎn)化為一個(gè)請(qǐng)求NSMutableURLRequest * request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];if(serializationError){如果(失敗){#pragma clang診斷推送#pragma clang診斷忽略“-WgnU”//如果解析錯(cuò)誤贝椿,直接返回dispatch_async(self.completionQueue想括?:dispatch_get_main_queue(),^ {失斃硬(nil瑟蜈,serializationError);});#pragma clang診斷流行}返回零;}__block NSURLSessionDataTask * dataTask = nil;dataTask = [self dataTaskWithRequest:request上傳進(jìn)度:上傳進(jìn)度downloadProgress:downloadProgresscompletionHandler:^(NSURLResponse * __unused response,id responseObject渣窜,NSError * error){如果(錯(cuò)誤){如果(失斊谈){失敗(dataTask乔宿,錯(cuò)誤);}} else {如果(成功){成功(dataTask位迂,responseObject);}}}];返回dataTask;}
我們繼續(xù)往下看:當(dāng)解析錯(cuò)誤,我們直接調(diào)用failuer失敗模塊回去,里面有個(gè)self.completionQueue,這是我們自定義的掂林,這個(gè)是gcd隊(duì)列臣缀,如果設(shè)置就從這個(gè)隊(duì)列中回掉了,不從主隊(duì)列中回掉了
實(shí)際上這個(gè)隊(duì)列還是很有用泻帮,有些公司有自己的一套數(shù)據(jù)加密解密解析模式精置,所以我們回掉過來的數(shù)據(jù)并不想在主隊(duì)列,我們可以在這個(gè)隊(duì)列對(duì)數(shù)據(jù)進(jìn)行解析锣杂,然后在回到主線程中氯窍。
(NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)請(qǐng)求uploadProgress:(可為空(void)(^)(NSProgress * uploadProgress))uploadProgressBlockdownloadProgress:(可空(void)(^)(NSProgress * downloadProgress))downloadProgressBlockcompletionHandler:(可空(void)(^)(NSURLResponse *響應(yīng),id _Nullable響應(yīng)對(duì)象蹲堂,NSError * _Nullable錯(cuò)誤))completionHandler {__block NSURLSessionDataTask * dataTask = nil;//第一件事,創(chuàng)建NSURLSessionDataTask贝淤,里面適配了iOS8上以下taskIdentifiers柒竞,函數(shù)創(chuàng)建任務(wù)對(duì)象。//其實(shí)現(xiàn)應(yīng)該是因?yàn)閕OS 8.0以下版本中會(huì)并發(fā)地創(chuàng)建多個(gè)任務(wù)對(duì)象播聪,而同步有沒有好朽基,導(dǎo)致taskIdentifiers不唯一...這邊做了一個(gè)串行處理url_session_manager_create_task_safely(^ {dataTask = [self.session dataTaskWithRequest:request];});[self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler];返回dataTask;}
我們注意到這個(gè)方法非常簡單,就調(diào)用了一個(gè)url_session_manager_create_task_safely()函數(shù)离陶,傳了一個(gè)塊進(jìn)去座里就是iOS的原生生成dataTask的方法稼虎。此外,還調(diào)用了一個(gè)addDelegateForDataTask的方法招刨。
我們到這先到這個(gè)函數(shù)里去看看:
static void url_session_manager_create_task_safely(dispatch_block_t block){if(NSFoundationVersionNumber
方法非常簡單霎俩,關(guān)鍵是理解這么做的目的:為什么我們不直接去調(diào)用
dataTask = [self.session dataTaskWithRequest:request];
非要繞這么一圈,我們點(diǎn)進(jìn)去錯(cuò)誤日志里看看沉眶,原來這是為了適配iOS8上的以下打却,創(chuàng)建會(huì)話的時(shí)候,偶發(fā)的情況會(huì)出現(xiàn)會(huì)話的屬性taskIdentifier這個(gè)值不唯一谎倔,而這個(gè)taskIdentifier是我們后面來映射代表的關(guān)鍵柳击,所以它必須是唯一的。
具體原因應(yīng)該是NSURLSession內(nèi)部去生成任務(wù)的時(shí)候是用多線程并發(fā)去執(zhí)行的片习。想通了這一點(diǎn)捌肴,我們就很好解決了,我們只需要在iOS8上以下同步串行的去生成任務(wù)就可以防止這一問題發(fā)生(如果還是不理解同步串行的原因藕咏,可以看看注釋)状知。
題外話:很多同學(xué)都會(huì)抱怨為什么同步我從來用不到,看侈离,有用到的地方了吧试幽,很多東西不是沒用,而只是你想不到怎么用。
我們接著看到:
[self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler];
調(diào)用到:
- (void)addDelegateForDataTask:(NSURLSessionDataTask *)dataTaskuploadProgress:(可為空(void)(^)(NSProgress * uploadProgress))uploadProgressBlockdownloadProgress:(可空(void)(^)(NSProgress * downloadProgress))downloadProgressBlockcompletionHandler:(void(^)(NSURLResponse * response铺坞,id responseObject起宽,NSError * error))completionHandler{AFURLSessionManagerTaskDelegate * delegate = [[AFURLSessionManagerTaskDelegate alloc] init];// AFURLSessionManagerTaskDelegate與AFURLSessionManager建立相互的關(guān)系delegate.manager = self;delegate.completionHandler = completionHandler;//這個(gè)taskDescriptionForSessionTasks用來發(fā)送開始和掛起通知的時(shí)候會(huì)用到,就是用這個(gè)值來張貼通知济榨,來兩者對(duì)應(yīng)dataTask.taskDescription = self.taskDescriptionForSessionTasks;// *****將AF委托對(duì)象與dataTask建立關(guān)系[self setDelegate:delegate forTask:dataTask];//設(shè)置AF委托的上傳進(jìn)度坯沪,下載進(jìn)度塊。delegate.uploadProgressBlock = uploadProgressBlock;delegate.downloadProgressBlock = downloadProgressBlock;}
總結(jié)一下:
1)這個(gè)方法擒滑,生成了一個(gè)AFURLSessionManagerTaskDelegate腐晾,這個(gè)其實(shí)就是AF的自定義代理。我們請(qǐng)求傳來的參數(shù)丐一,都賦值給這個(gè)AF的代理了藻糖。
2)delegate.manager = self;代理把AFURLSessionManager這個(gè)類作為屬性了,我們可以看到:
@屬性(非原子库车,弱)AFURLSessionManager *管理器;
這個(gè)屬性是弱引用的巨柒,所以不會(huì)存在循環(huán)引用的問題。
3)我們調(diào)用了[self setDelegate:delegate forTask:dataTask];
我們進(jìn)去看看這個(gè)方法做了什么:
- (void)setDelegate:(AFURLSessionManagerTaskDelegate *)委托forTask:(NSURLSessionTask *)任務(wù){(diào)//斷言柠衍,如果沒有這個(gè)參數(shù)洋满,調(diào)試下墜毀在這NSParameterAssert(任務(wù));NSParameterAssert(代表);//加鎖保證字典線程安全[self.lock lock];//將AF委托放入以taskIdentifier標(biāo)記的詞典中(同一個(gè)NSURLSession中的taskIdentifier是唯一的)self.mutableTaskDelegatesKeyedByTaskIdentifier [@(task.taskIdentifier)] = delegate;//為AF代表設(shè)置任務(wù)的進(jìn)度監(jiān)聽[委托setupProgressForTask:任務(wù)];//添加任務(wù)開始和暫停的通知[self addNotificationObserverForTask:task];[self.lock解鎖];}
這個(gè)方法主要就是把AF代理和任務(wù)建立映射,存在了一個(gè)我們事先聲明好的字典里珍坊。
而要加鎖的原因是因?yàn)楸旧砦覀冞@個(gè)字典屬性是可變的牺勾,是線程不安全的。而我們對(duì)這些方法的調(diào)用阵漏,確實(shí)是會(huì)在復(fù)雜的多線程環(huán)境中驻民,后面會(huì)仔細(xì)提到線程問題。
還有個(gè)[delegate setupProgressForTask:task];我們到方法里去看看:
- (void)setupProgressForTask:(NSURLSessionTask *)task {__weak __typeof __(task)weakTask = task;//拿到上傳下載期望的數(shù)據(jù)大小self.uploadProgress.totalUnitCount = task.countOfBytesExpectedToSend;self.downloadProgress.totalUnitCount = task.countOfBytesExpectedToReceive;//將上傳與下載進(jìn)度和任務(wù)綁定在一起袱饭,直接取消掛起恢復(fù)進(jìn)度條川无,可以取消...任務(wù)[self.uploadProgress setCaslable:YES];[self.uploadProgress setCancellationHandler:^ {__typeof __(weakTask)strongTask = weakTask;[strong任務(wù)取消];}];[self.uploadProgress setPausable:YES];[self.uploadProgress setPausingHandler:^ {__typeof __(weakTask)strongTask = weakTask;[strongTask暫停];}];如果([self.uploadProgress respondsToSelector:@selector(setResumingHandler :)]){[self.uploadProgress setResumingHandler:^ {__typeof __(weakTask)strongTask = weakTask;[strongTask簡歷];}];}[self.downloadProgress setCancellable:YES];[self.downloadProgress setCancellationHandler:^ {__typeof __(weakTask)strongTask = weakTask;[strong任務(wù)取消];}];[self.downloadProgress setPausable:YES];[self.downloadProgress setPausingHandler:^ {__typeof __(weakTask)strongTask = weakTask;[strongTask暫停];}];如果([self.downloadProgress respondsToSelector:@selector(setResumingHandler :)]){[self.downloadProgress setResumingHandler:^ {__typeof __(weakTask)strongTask = weakTask;[strongTask簡歷];}];}//觀察任務(wù)的這些屬性[任務(wù)addObserver:selfforKeyPath:NSStringFromSelector(@selector(countOfBytesReceived))選項(xiàng):NSKeyValueObservingOptionNew上下文:NULL];[任務(wù)addObserver:selfforKeyPath:NSStringFromSelector(@selector(countOfBytesExpectedToReceive))選項(xiàng):NSKeyValueObservingOptionNew上下文:NULL];[任務(wù)addObserver:selfforKeyPath:NSStringFromSelector(@selector(countOfBytesSent))選項(xiàng):NSKeyValueObservingOptionNew上下文:NULL];[任務(wù)addObserver:selfforKeyPath:NSStringFromSelector(@selector(countOfBytesExpectedToSend))選項(xiàng):NSKeyValueObservingOptionNew上下文:NULL];//觀察進(jìn)度這兩個(gè)屬性[self.downloadProgress addObserver:selfforKeyPath:NSStringFromSelector(@selector(fractionCompleted))選項(xiàng):NSKeyValueObservingOptionNew上下文:NULL];[self.uploadProgress addObserver:selfforKeyPath:NSStringFromSelector(@selector(fractionCompleted))選項(xiàng):NSKeyValueObservingOptionNew上下文:NULL];}
這個(gè)方法也非常簡單,主要做了以下幾件事:
1)設(shè)置downloadProgress與uploadProgress的一些屬性虑乖,并且把兩個(gè)和任務(wù)的任務(wù)狀態(tài)綁定在了一起懦趋。注意這兩個(gè)都是NSProgress的實(shí)例對(duì)象,(這里可能又一群小伙楞在這了疹味,這是個(gè)什么...)簡單來說仅叫,這就是iOS7引進(jìn)的一個(gè)用來管理進(jìn)度的類,可以開始糙捺,暫停诫咱,取消,完整的對(duì)應(yīng)了任務(wù)的各種狀態(tài)洪灯,當(dāng)進(jìn)度進(jìn)行各種操作的時(shí)候坎缭,任務(wù)也會(huì)引發(fā)對(duì)應(yīng)操作。
2)給的任務(wù)和進(jìn)度的各個(gè)屬及添加志愿監(jiān)聽,至于監(jiān)聽了干什么用掏呼,我們接著往下看:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {//是任務(wù)如果([object isKindOfClass:[NSURLSessionTask class]] || [object isKindOfClass:[NSURLSessionDownloadTask class]]){//給進(jìn)度條賦新值如果([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesReceived))]){self.downloadProgress.completedUnitCount = [更改[NSKeyValueChangeNewKey] longLongValue];} else if([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesExpectedToReceive))]){self.downloadProgress.totalUnitCount = [change [NSKeyValueChangeNewKey] longLongValue];} else if([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesSent))]){self.uploadProgress.completedUnitCount = [更改[NSKeyValueChangeNewKey] longLongValue];} else if([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesExpectedToSend))]){self.uploadProgress.totalUnitCount = [change [NSKeyValueChangeNewKey] longLongValue];}}//上面的賦新值會(huì)觸發(fā)這兩個(gè)坏快,調(diào)用塊回調(diào),用戶拿到進(jìn)度else if([object isEqual:self.downloadProgress]){if(self.downloadProgressBlock){self.downloadProgressBlock(對(duì)象);}}else if([object isEqual:self.uploadProgress]){if(self.uploadProgressBlock){self.uploadProgressBlock(對(duì)象);}}}
方法非常簡單直觀憎夷,主要就是如果任務(wù)觸發(fā)志愿莽鸿,則給進(jìn)度進(jìn)度賦值,應(yīng)為賦值了拾给,所以會(huì)觸發(fā)進(jìn)步的志愿祥得,也會(huì)調(diào)用到這里,然后去執(zhí)行我們傳進(jìn)來的downloadProgressBlock和uploadProgressBlock蒋得。主要的作用就是為了讓進(jìn)度實(shí)時(shí)的傳遞级及。
主要是觀摩一下大神的寫代碼的結(jié)構(gòu),這個(gè)解耦的編程思想额衙,不愧是大神...
還有一點(diǎn)需要注意:我們之前的setProgress和這個(gè)志愿監(jiān)聽创千,都是在我們AF自定義的委托內(nèi)的,是有一個(gè)任務(wù)就會(huì)有一個(gè)代表的所以說我們是每個(gè)任務(wù)都會(huì)去監(jiān)聽這些屬性入偷,分別在各自的AF代理內(nèi)⌒涤矗看到這疏之,可能有些小伙伴會(huì)有點(diǎn)亂,沒關(guān)系暇咆。等整個(gè)講完之后我們還會(huì)詳細(xì)的去講捋一捋經(jīng)理锋爪,任務(wù),還有AF自定義代理三者之前的對(duì)應(yīng)關(guān)系爸业。
到這里我們整個(gè)對(duì)任務(wù)的處理就完成了其骄。
2,HTTPS認(rèn)證- (void)URLSession:(NSURLSession *)會(huì)話didReceiveChallenge:(NSURLAuthenticationChallenge *)挑戰(zhàn)completionHandler:(void(^)(NSURLSessionAuthChallengeDisposition disposition扯旷,NSURLCredential * credential))completionHandler{//挑戰(zhàn)處理類型為默認(rèn)/ *NSURLSessionAuthChallengePerformDefaultHandling:默認(rèn)方式處理NSURLSessionAuthChallengeUseCredential:使用指定的證書NSURLSessionAuthChallengeCancelAuthenticationChallenge:取消挑戰(zhàn)* /NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;__block NSURLCredential * credential = nil;// sessionDidReceiveAuthenticationChallenge是自定義方法拯爽,用來如何應(yīng)對(duì)服務(wù)器端的認(rèn)證挑戰(zhàn)如果(self.sessionDidReceiveAuthenticationChallenge){disposition = self.sessionDidReceiveAuthenticationChallenge(session,challenge钧忽,&credential);} else {//此處服務(wù)器要求客戶端的接收認(rèn)證挑戰(zhàn)方法是NSURLAuthenticationMethodServerTrust//也就是說服務(wù)端需要客戶端返回一個(gè)根據(jù)認(rèn)證挑戰(zhàn)的保護(hù)空間提供的信任(即challenge.protectionSpace.serverTrust)產(chǎn)生的挑戰(zhàn)證書毯炮。//而這個(gè)證書就需要使用credentialForTrust:來創(chuàng)建一個(gè)NSURLCredential對(duì)象如果([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]){//基于客戶端的安全策略來決定是否信任該服務(wù)器,不信任的話耸黑,也就沒必要響應(yīng)挑戰(zhàn)如果([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]){//創(chuàng)建挑戰(zhàn)證書(注:挑戰(zhàn)方式為UseCredential和PerformDefaultHandling都需要新建挑戰(zhàn)證書)credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];//確定挑戰(zhàn)的方式如果(憑證){//證書挑戰(zhàn)處置= NSURLSessionAuthChallengeUseCredential;} else {//默認(rèn)挑戰(zhàn)唯一區(qū)別桃煎,下面少了這一步!處置= NSURLSessionAuthChallengePerformDefaultHandling;}} else {//取消挑戰(zhàn)處置= NSURLSessionAuthChallengeCancelAuthenticationChallenge;}} else {//默認(rèn)挑戰(zhàn)方式處置= NSURLSessionAuthChallengePerformDefaultHandling;}}//完成挑戰(zhàn)if(completionHandler){completionHandler(處置大刊,憑證);}