web-dev-qa-db-ja.com

Objective-Cコールバックハンドラー

動作するようになったコールバックメソッドがありますが、値を渡す方法を知りたいです。

私が持っているのはこれです:

@interface DataAccessor : NSObject
{
    void (^_completionHandler)(Account *someParameter);

}


- (void) signInAccount:(void(^)(Account *))handler;

上記のコードは機能しますが、値をメソッドに渡したいです。これはどのように見えますか?何かのようなもの:

- (void) signInAccount:(void(^)(Account *))handler user:(NSString *) userName pass:(NSString *) passWord;

34
Jesse

あなたがそこで何をしようとしているのか完全にはわかりません-あなたのコールバックはブロックです...それは意図的ですか?私はあなたのメソッドが次のようになることを期待しています:

- (void)signInAccountWithUserName:(NSString *)userName password:(NSString *)password;

コールバックの目的が、完了時にメソッドを呼び出すときに指定された追加コードを実行することである場合、ブロックが役立ちます。たとえば、メソッドは次のようになります。

- (void)signInAccountWithUserName:(NSString *)userName
                         password:(NSString *)password
                       completion:(void (^)(void))completionBlock
{
    // ...
    // Log into the account with `userName` and `password`...
    //

    if (successful) {
        completionBlock();
    }
}

そして、次のようにメソッドを呼び出します。

[self signInAccountWithUserName:@"Bob"
                       password:@"BobsPassword"
                     completion:^{
                         [self displayBalance];  // For example...
                     }];

このメソッド呼び出しは、ユーザーをアカウントにログインさせ、それが完了するとすぐに残高を表示します。これは明らかに不自然な例ですが、うまくいけばアイデアが得られます。

これが意図したものではない場合は、上記のようなメソッドシグネチャを使用します。


編集(successful変数を使用したより良い例):

より良い設計は、ログインがどの程度うまく行ったかを示すブール値を完了ブロックに戻すことです。

- (void)signInAccountWithUserName:(NSString *)userName
                         password:(NSString *)password
                       completion:(void (^)(BOOL success))completionBlock
{
    // Log into the account with `userName` and `password`...
    // BOOL loginSuccessful = [LoginManager contrivedLoginMethod];

    // Notice that we are passing a BOOL back to the completion block.
    if (completionBlock != nil) completionBlock(loginSuccessful);
}

また、今回は、呼び出す前にcompletionBlockパラメーターがnilではないことを確認していることがわかります。これは、メソッドの使用を許可する場合に重要ですwithout完了ブロック。このメソッドは次のように使用できます。

[self signInAccountWithUserName:@"Bob"
                       password:@"BobsPassword"
                     completion:^(BOOL success) {
                         if (success) {
                             [self displayBalance];
                         } else {
                             // Could not log in. Display alert to user.
                         }
                     }];

さらに良いのは(例の範囲を許せば!)、ユーザーが失敗の理由を知ることが有用である場合、NSErrorオブジェクトを返します。

- (void)signInAccountWithUserName:(NSString *)userName
                         password:(NSString *)password
                       completion:(void (^)(NSError *error))completionBlock
{
    // Attempt to log into the account with `userName` and `password`...

    if (loginSuccessful) {
        // Login went ok. Call the completion block with no error object.
        if (completionBlock != nil) completionBlock(nil);
    } else {
        // Create an error object. (N.B. `userInfo` can contain lots of handy 
        // things! Check out the NSError Class Reference for details...)
        NSInteger errorCode;
        if (passwordIncorrect) {
            errorCode = kPasswordIncorrectErrorCode;
        } else {
            errorCode = kUnknownErrorCode;
        }
        NSError *error = [NSError errorWithDomain:MyLoginErrorDomain code:errorCode userInfo:nil];
        if (completionBlock != nil) completionBlock(error);
    }
}

呼び出し元は、完了ブロックのNSErrorを使用して、処理方法を決定できます(ほとんどの場合、ユーザーに何が悪かったのかを説明するため)。この種類のパターンは、あまり一般的ではありません(完全に有効ですが)。ほとんどの場合、NSErrorsは、NSFileWrappers -initWithURL:options:error:メソッドなどのポインター間接指定によって返されます。

NSError *error;
NSFileWrapper *fw = [[NSFileWrapper alloc] initWithURL:url options:0 error:&error];
// After the above method has been called, `error` is either `nil` (if all went well),
// or non-`nil` (if something went wrong).

ただし、ログインの例では、ログイン試行が完了するまでにある程度の時間がかかると予想されます(たとえば、オンラインアカウントへのログイン)。したがって、エラーを返す完了ハンドラーを使用することは完全に合理的です。

116
Stuart