web-dev-qa-db-ja.com

NSURLSessionDataTask完了ハンドラーからNSDataを返す方法

ポストWebサービスを呼び出すために使用できる簡単なクラスを作成しようとしています。

NSDataを返すことができないことを除いて、すべてが完全に機能しています。

これは私のコードです:

+ (NSData *)postCall:(NSDictionary *)parameters fromURL:(NSString *)url{
    NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
    NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration];
    NSMutableArray *pairs = [[NSMutableArray alloc]init];
    for(NSString *key in parameters){
        [pairs addObject:[NSString stringWithFormat:@"%@=%@", key, parameters[key]]];
    }
    NSString *requestParameters = [pairs componentsJoinedByString:@"$"];
    NSURL *nsurl = [NSURL URLWithString:url];
    NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:nsurl];
    [urlRequest setHTTPMethod:@"POST"];
    [urlRequest setHTTPBody:[requestParameters dataUsingEncoding:NSUTF8StringEncoding]];
    NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:urlRequest completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        //return data;
    }];
    [dataTask resume];

    return nil;
}

//return dataがありますが、このエラーが発生することに注意してください

 Incompatible block pointer types sending 'NSData *(^)(NSData *__strong, NSURLResponse *__strong, NSError *__strong)' to parameter of type 'void (^)(NSData *__strong, NSURLResponse *__strong, NSError *__strong)'

私の質問は:

  1. 私のやり方は良いですか、それとも将来問題を引き起こすでしょうか?ダウンロードする画像がなく、アップロードするものもありません。単純な文字列データを送信し、単純な文字列データを受信するだけです。または、各関数のそのコードを個別に使用する方がよいでしょうか?

  2. どうすればデータを返すことができますか?

9
Carol Smith

データを返すだけでは不十分です(NSURLSessionDataTaskは非同期で実行されるため)。 completionHandlerメソッドのdataTaskWithRequestと同様に、独自の完了ブロックパターンを使用することをお勧めします。

したがって、独自のブロックパラメータをメソッドに追加し、dataTaskWithRequestメソッドのcompletionHandler内から呼び出します。

+ (NSURLSessionDataTask *)postCall:(NSDictionary *)parameters fromURL:(NSString *)url completionHandler:(void (^)(NSData *data, NSURLResponse *response, NSError *error))completionHandler {

    // create your request here ...

    NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:urlRequest completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        if (completionHandler)
            completionHandler(data, response, error);
    }];

    [dataTask resume];

    return dataTask;
}

または、このdataTaskWithRequestはバックグラウンドスレッドで実行されるため、完了ハンドラーをメインキューにディスパッチすることを確認すると便利な場合があります。

NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:urlRequest completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
    if (completionHandler)
        dispatch_async(dispatch_get_main_queue(), ^{
            completionHandler(data, response, error);
        });
}];

余談ですが、上記のようにNSURLSessionDataTask参照を返すのは良いことだと思います。そうすれば、(a)呼び出し元はデータタスクが正常に作成されたことを確認できます。 (b)NSURLSessionTask参照があり、将来、何らかの理由でリクエストをキャンセルできるようにしたい場合(たとえば、ユーザーがView Controllerを却下した場合)にタスクをキャンセルするために使用できます。リクエストの発行元)。

とにかく、次にこれを次のように呼び出します。

NSURLSessionTask *task = [MyClass postCall:parameters fromURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
    // put whatever code you want to perform when the asynchronous data task completes
}];

if (!task) {
    // handle failure to create task any way you want
}

あなたが尋ねる:

私のやり方は良いですか、それとも将来問題を引き起こすでしょうか?ダウンロードする[an]画像がなく、アップロードするものもありません。[いくつかの]単純な文字列データを送信し、[単純な]文字列データを受信するだけです。または、各関数のそのコードを個別に使用する方がよいでしょうか?

単純な文字列データを受け取っている場合は、JSON形式で応答を作成し、postCallに完了ブロックを配置してNSJSONSerializationを使用して応答を抽出することをお勧めします。 JSONを使用すると、アプリが成功した応答と、文字列応答を返す可能性のあるサーバー関連のさまざまな問題を簡単に区別できます。

したがって、次のような応答を返すようにサーバーコードを変更したとします。

{"response":"some text"}

次に、postCallを変更して、次のようにその応答を解析できます。

+ (NSURLSessionDataTask *)postCall:(NSDictionary *)parameters fromURL:(NSString *)url completionHandler:(void (^)(NSString *responseString, NSError *error))completionHandler {

    // create your request here ...

    NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:urlRequest completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        if (completionHandler) {
            if (error) {
                completionHandler(nil, error);
            } else {
                NSError *parseError = nil;
                NSDictionary *responseDictionary = [NSJSONSerialization JSONObjectWithData:data options:0 error:&parseError];

                completionHandler(responseDictionary[@"response"], parseError);
            }
        }
    }];

    [dataTask resume];

    return dataTask;
}

根本的な質問に関しては、postCallのようなメソッドが理にかなっているかどうか、はい、リクエストの作成の詳細を1つのメソッドにまとめることは完全に理にかなっていると思います。あなたの実装における私のマイナーな予約は、それをインスタンスメソッドではなくクラスメソッドにするというあなたの決定でした。現在、リクエストごとに新しいNSURLSessionを作成しています。 postCallを(必要に応じてシングルトンの)インスタンスメソッドにしてから、セッションをクラスプロパティとして保存し、一度設定してから後続のクエリで再利用することをお勧めします。

8
Rob

ブロック方式を使用する必要があります。

最初にブロックを定義します

typedef void (^OnComplete) (NSData *data);

次の方法を使用します

+ (void)postCall:(NSDictionary *)parameters fromURL:(NSString *)url withBlock:(OnComplete)block; {

     NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
        NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration];
        NSMutableArray *pairs = [[NSMutableArray alloc]init];
        for(NSString *key in parameters){
            [pairs addObject:[NSString stringWithFormat:@"%@=%@", key, parameters[key]]];
        }
        NSString *requestParameters = [pairs componentsJoinedByString:@"&"];
        NSURL *myURL = [NSURL URLWithString:url];
        NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:myURL];
        [urlRequest setHTTPMethod:@"POST"];
        [urlRequest setHTTPBody:[requestParameters dataUsingEncoding:NSUTF8StringEncoding]];
        NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:urlRequest completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
            block(data);
        }];
        [dataTask resume];
   }
1
Sunny Shah