GCDによるパケット同時ネットワーク要求の実装とパッケージング

7442 ワード

コンカレント・グループ・リクエストを使用する理由
実際の開発では、ページのロード時にネットワークリクエストで対応するデータを取得し、いくつかの操作を行います.ロードされたコンテンツは、いくつかのリクエストのデータを組み合わせて作成する必要があります.例えば、2つのリクエストAとBがあります.私たちは通常、手間を省くために、BリクエストをAリクエストの成功したコールバックで開始します.Bの成功したコールバックでデータを組み合わせると、明らかな問題があります.
  • リクエストが多い場合は、ネストされたリクエスト
  • を多く書く必要があります.
  • 最後のリクエストを除く前のリクエストが失敗した場合、後のリクエストは実行されず、データは
  • をロードできません.
  • 要求が同期化するのは最大の問題であり、ネットワーク差の場合、n個の要求がある場合、ユーザが同時要求のn倍の時間を待つことを意味する
  • .
    dispatch_グループ
    よく知っているdispatch_グループの学生は直接この節をスキップすることができます.
    このようなlowの同期要求は当然受け入れられないので、これらの要求を同時に実行し、すべての要求が正常にコールバックされた後、コンテンツのロードやその他の操作を行い、再三考慮してGCDのdispatch_を選択します.groupが一番便利です.
    A dispatch group is a mechanism for monitoring a set of blocks. Your application can monitor the blocks in the group synchronously or asynchronously depending on your needs. By extension, a group can be useful for synchronizing for code that depends on the completion of other tasks.
    dispatch_groupはblockを監視するために生まれ、アップルもあなたの操作が他のいくつかのタスクの完了に依存している場合、dispatch_を使用することを提案しています.group.
    dispatch_groupには通常2つの使い方があります.
  • dispatch_group_async(, , ) dispatch_を作成group_tは、同時の操作をblockに入れ、dispatch_group_notify(, , )のblockで複数組のblockの実行が完了した後の操作を実行し、ネットワークリクエストにとって、リクエストが発行されたときに実行が完了したとしても、すなわちblockにblockがある場合、ネットワークリクエストのコールバックを待つことはないので、私たちのニーズを満たしていない.だから別の使い方を採用します:
  • dispatch_group_enter() dispatch_group_leave()以下はdispatch_group_enterの公式ドキュメント解釈:
  • Calling this function increments the current count of outstanding tasks in the group. Using this function (with dispatch_group_leave) allows your application to properly manage the task reference count if it explicitly adds and removes tasks from the group by a means other than using the dispatch_group_async function. A call to this function must be balanced with a call to dispatch_group_leave. You can use this function to associate a block with more than one group at the same time.
    dispatch_groupには実際にtask reference count(タスクカウンタ)があり、enter時にreference count+1、leave時にreference count-1、enterとleaveは協力して使用しなければならない.enterは何回かleaveしなければならない.そうしないとgroupはずっと存在し、dispatch_group_notifyもトリガーされない.
    すべてのenterのblockがleaveされるとdispatch_が実行されますgroup_notifyのblock、この方法は明らかにもっと柔軟です.
    もちろん、ネットワークリクエストの前にenterを実行し、各リクエストの成功または失敗したコールバックを実行した後にleaveを実行し、notifyでコンテンツロードを実行することができます.これで、同時ネットワークグループリクエストの問題は解決されましたが、まだ少し不快です.グループリクエストを開始するたびにgroupを作成し、enterとleaveを書く必要があります.面倒でリクエストの多重化にも不利です.自然に私たちは彼をカプセル化したいと思っています.元のネットワークリクエストコードを変更することなく、ネットワークリクエストをグループに追加することができます.
    [[NetworkTool sharedInstance] postForGroup:^{
            [request1 success:^(id responseObject) {
    
            } failure:^(NSError *error) {
    
            }];
            [request2 success:^(id responseObject) {
    
            } failure:^(NSError *error) {
    
            }];
        } success:^{
            // group success
        } failure:^(NSArray *errorArray) {
            // group failure
        }];
    

    グループリクエストのカプセル化
    もし私がこのような効果を達成したいならば、きっとネットの単例層に行って下層要求に対していくつか修正をしなければなりませんが、私はまた既存の下層要求方法を変えたくないので、私はmethod_exchangeImplementations(, )という関数を採用して、既存の下層要求方法に基づいて、一連の要求方法を実現しました.グループリクエストを送信するときは,元のメソッドを置き換え,グループリクエストがすべて送信された後,元のメソッドに戻す.
    しかし、ここにはいくつかの恐ろしい穴があります.使い方の置き換えは危険ですから.
  • 私が置換をした後、正常な非グループネットワークリクエストも置換後の方法を歩きますが、私は彼が置換後の方法を歩く必要はありません.
  • もし私が同時に複数のグループ要求を開始したら、グループとグループの間でどのように区別するか、異なるグループは相互に影響を与えるべきではありません.

  • 最初はmarkを要求して、彼がどのグループに属しているかをマークすることを考えていましたが、これはあなたが要求をオブジェクトにカプセル化する必要があります.もしあなたのプロジェクトが私と同じであれば、要求を出すときはただ方法を実行するだけで、彼にマークを付けるのはよくありません.
    頭の嵐の後、私はキューでgorupを区別することにした.
    具体的には、groupを作成するときにキューを開き、キューにgroupプロパティを動的に追加し、1つのキューが1つのgroupに対応します.キューにメソッドを置き換え、グループ内のリクエストを開始し、元のメソッドに置き換えます.このように置換方法では、現在のキューを取得するだけでgroupを取得することができ、groupがnilであれば、正常な非グループ要求であることを示し、original methodを実行することができる.groupがnilでない場合、groupに基づいてenterとleaveを行い、groupごとに区別することができます.
    グループを作成するときに、グループに要求されたerrorを記録するerrorArrayプロパティをグループに動的に追加します.errorArrayが空でない限り、グループに失敗したblockを実行します.
    完全なコードを添付します.
    typedef void(^BlockAction)();
    typedef void(^GroupResponseFailure)(NSArray * errorArray);
    static char groupErrorKey;
    static char queueGroupKey;
    

    単一の例で下位ネットワーク要求を置き換えるためのグループ要求方法
    - (void)sendPOSTRequestInGroup:(NSString *)strURL withData:(NSDictionary *)data paramForm:(ParamForm)paramForm withTimeout:(NSTimeInterval)timeout showAlert:(BOOL)show  success:(BlockResponse)success failure:(BlockResponseFailure)failure {
        
        dispatch_group_t group = objc_getAssociatedObject([NSOperationQueue currentQueue], &queueGroupKey);
        
        //        
        if (group == nil) {
            //   original method
            [self sendPOSTRequestInGroup:strURL withData:data paramForm:paramForm withTimeout:timeout showAlert:show success:success failure:failure];
            return;
        }
        
        dispatch_group_enter(group);
        //   original method
        [self sendPOSTRequestInGroup:strURL withData:data paramForm:paramForm withTimeout:timeout showAlert:show success:^(id responseObject) {
    
            if (success) {
                success(responseObject);
            }
    
            dispatch_group_leave(group);
        } failure:^(NSError *error) {
            NSMutableArray *arrayM = objc_getAssociatedObject(group, &groupErrorKey);
            [arrayM addObject:error];
    
            if (failure) {
                failure(error);
            }
    
            dispatch_group_leave(group);
        }];
    }
    

    外部に提供されるグループリクエスト方法
    - (void)sendGroupPostRequest:(BlockAction)requests success:(BlockAction)success failure:(GroupResponseFailure)failure {
        if (requests == nil) {
            return;
        }
        
        dispatch_group_t group = dispatch_group_create();
        objc_setAssociatedObject(group, &groupErrorKey, [NSMutableArray array], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
        
        Method originalPost = class_getInstanceMethod(self.class, @selector(sendPOSTRequest:withData:paramForm:withTimeout:showAlert:success:failure:));
        Method groupPost = class_getInstanceMethod(self.class, @selector(sendPOSTRequestInGroup:withData:paramForm:withTimeout:showAlert:success:failure:));
        
        NSOperationQueue *queue = [[NSOperationQueue alloc] init];
        objc_setAssociatedObject(queue, &queueGroupKey, group, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
        queue.qualityOfService = NSQualityOfServiceUserInitiated;
        queue.maxConcurrentOperationCount = 3;
        
        [queue addOperationWithBlock:^{
            
            method_exchangeImplementations(originalPost, groupPost);
            //                  
            requests();
            //            original method,      ,           
            method_exchangeImplementations(originalPost, groupPost);
            
            dispatch_group_notify(group, dispatch_get_main_queue(), ^{
                NSMutableArray *arrayM = objc_getAssociatedObject(group, &groupErrorKey);
                //            ,        
                if (arrayM.count > 0) {
                    if (failure) {
                        failure(arrayM.copy);
                    }
                } else if(success) {
                    success();
                }
            });
        }];
    }
    

    このパッケージを経て、私は使用する時、- (void)sendGroupPostRequest:(BlockAction)requests success:(BlockAction)success failure:(GroupResponseFailure)failureという方法のrequests blockの中で、ネットの要求を投げ込んで、もともと書いた要求は何の修正もしなくても、要求自体のsuccessとfailureも実行することができて、success blockの中でグループが成功した後にしなければならないことを書いて、例えばコンテンツのロード、failure blockの中ですべての要求のerrorを手に入れることができて、相応の処置をする.
    Demo:https://github.com/suruihai/-GCD-Demo/tree/master