3

私は完了ハンドラがどのように機能するかを尋ね、理解しようとしています。私はかなりの数を使用し、多くのチュートリアルを読みました。ここで使用するものを投稿しますが、他の人のコードを参考にせずに自分で作成できるようにしたいと考えています。

この呼び出し元メソッドのこの完了ハンドラーを理解しています:

-(void)viewDidLoad{
[newSimpleCounter countToTenThousandAndReturnCompletionBLock:^(BOOL completed){
        if(completed){ 
            NSLog(@"Ten Thousands Counts Finished");
        }
    }];
}

次に、呼び出されたメソッドで:

-(void)countToTenThousandAndReturnCompletionBLock:(void (^)(BOOL))completed{
    int x = 1;
    while (x < 10001) {
        NSLog(@"%i", x);
        x++;
    }
    completed(YES);
}

それから、私は多くのSO投稿に基づいてこれを思いつきました:

- (void)viewDidLoad{
    [self.spinner startAnimating];
    [SantiappsHelper fetchUsersWithCompletionHandler:^(NSArray *users) {
        self.usersArray = users;
        [self.tableView reloadData];
    }];
}

このメソッドを呼び出した後、受信したデータ ユーザーでテーブルビューをリロードします。

typedef void (^Handler)(NSArray *users);

+(void)fetchUsersWithCompletionHandler:(Handler)handler {
    NSURL *url = [NSURL URLWithString:@"http://www.somewebservice.com"];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData timeoutInterval:10];
    [request setHTTPMethod: @"GET"];
    **// We dispatch a queue to the background to execute the synchronous NSURLRequest**
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
        // Perform the request
        NSURLResponse *response;
        NSError *error = nil;
        NSData *receivedData = [NSURLConnection sendSynchronousRequest:request
                                                     returningResponse:&response
                                                                 error:&error];
        if (error) { **// If an error returns, log it, otherwise log the response**
            // Deal with your error
            if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
                NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)response;
                NSLog(@"HTTP Error: %d %@", httpResponse.statusCode, error);
                return;
            }
            NSLog(@"Error %@", error);
            return;
        }
        **// So this line won't get processed until the response from the server is returned?**
        NSString *responseString = [[NSString alloc] initWithData:receivedData encoding:NSUTF8StringEncoding];

        NSArray *usersArray = [[NSArray alloc] init];
        usersArray = [NSJSONSerialization JSONObjectWithData:[responseString dataUsingEncoding:NSASCIIStringEncoding] options:0 error:nil];
        // Finally when a response is received and this line is reached, handler refers to the block passed into this called method...so it dispatches back to the main queue and returns the usersArray
        if (handler){
            dispatch_sync(dispatch_get_main_queue(), ^{
            handler(usersArray);
            });
        }
    });
}

カウンターの例では、呼び出されたメソッド (渡されたブロックを含む) が完了するまでループを終了しないことがわかります。したがって、「完了」部分は、実際には、渡されたブロックではなく、呼び出されたメソッド内のコードに依存しますか?

この場合、「完了」部分は、NSURLRequest への呼び出しが同期的であるという事実に依存します。非同期だったら?NSURLResponse によってデータが入力されるまで、ブロックの呼び出しを延期するにはどうすればよいでしょうか?

4

2 に答える 2

3

最初の例は正しく完全であり、完了ブロックを理解するための最良の方法です。彼らにはこれ以上の魔法はありません。それらは自動的に実行されることはありません。それらは、コードの一部がそれらを呼び出すときに実行されます。

お気づきのように、後者の例では、すべてが同期しているため、適切なタイミングで完了ブロックを呼び出すのは簡単です。非同期の場合は、ブロックをインスタンス変数に格納し、非同期操作が完了したときにそれを呼び出す必要があります。操作が完了したときに通知されるように手配するのはあなた次第です (おそらく完了ハンドラーを使用して)。

ブロックを ivar に格納するときは注意してください。あなたの例の1つは次のとおりです。

   self.usersArray = users;

への呼び出しselfにより、ブロックが保持されますself(呼び出し元のオブジェクト)。これにより、保持ループが簡単に作成されます。通常、次のように弱い参照を取得する必要がありますself

- (void)viewDidLoad{
  [self.spinner startAnimating];
  __weak typeof(self) weakSelf = self;
  [SantiappsHelper fetchUsersWithCompletionHandler:^(NSArray *users) {
    typeof(self) strongSelf = weakSelf;
    if (strongSelf) {
      [strongSelf setUsersArray:users];
      [[strongSelf tableView] reloadData];
    }
  }];
}

これは、weakSelf/strongSelf パターンのかなりペダンティックなバージョンであり、この場合はもう少し単純にすることができますが、必要なすべての部分を示しています。self保持ループを作成しないように、弱い参照を取ります。次に、完全なブロックで、強い参照を取得してself、ブロックの途中で消えないようにします。self次に、 が実際にまだ存在することを確認してから、先に進みます。(メッセージングは​​合法であるため、この特定のケースでnilはステップをスキップできた可能性があり、それは同じです。)strongSelf

于 2014-01-21T23:06:49.357 に答える
2

最初の例 (countToTenThousandAndReturnCompletionBLock) は、実際には同期メソッドです。ここでは、完了ハンドラーはあまり意味がありません。代わりに、仮想メソッドの直後にそのブロックを呼び出すこともできますcountToTenThousand(これは基本的に同じで、完了ハンドラーがないだけです)。

2 番目の例fetchUsersWithCompletionHandler:は非同期メソッドです。ただし、実際には非常に最適ではありません。

  1. リクエストが失敗した可能性があることを呼び出しサイトに何らかの方法で通知する必要があります。つまり、完了ハンドラに追加のパラメータを提供するか、たとえば "NSError* errorまたは単一のパラメータを指定しますid result。最初のケースでは、errorまたはarrayは notnilであり、2 番目のケースでは、単一のパラメータの結果はエラー オブジェクト (のようなものですNSError)または実際の結果(のようなものですNSArray)。

  2. リクエストが失敗した場合、呼び出しサイトにエラーを通知できません。

  3. コードの匂いがあります:

    実際のところ、システムによって実装される基盤となるネットワーク コードは非同期です。しかし、利用する便利なクラスメソッドsendSynchronousRequest:同期です。つまり、 の実装の詳細としてsendSynchronousRequest:、呼び出し元のスレッドは、ネットワーク応答の結果が利用可能になるまでブロックされます。そして、 this_blocking_ は待機のためだけにスレッド全体を占有します。スレッドの作成にはかなりのコストがかかり、この目的のためだけに使用するのはもったいないです。これは最初のコードの匂いです。はい、便利なクラス メソッドを使用sendSynchronousRequest:するだけで、それ自体が悪いプログラミング プラクティスです。

    次に、コード内で、この同期リクエストをキューにディスパッチすることで再び非同期にします。

    そのため、ネットワーク リクエストに非同期メソッド (例: sendAsynchronous...) を使用することをお勧めします。これは、完了ハンドラーを介して完了を通知すると考えられます。この完了ハンドラーは、実際の結果またはエラーのどちらを取得したかに注意して、完了ハンドラー パラメーター呼び出すことができます。

于 2014-01-21T23:18:03.913 に答える