web-dev-qa-db-ja.com

AFNetworkingでタイムアウトを設定する方法

私のプロジェクトはAFNetworkingを使用しています。

https://github.com/AFNetworking/AFNetworking

タイムアウトをダイヤルダウンするにはどうすればよいですか?インターネットに接続されていないAtmでは、約2分間のように感じてもフェイルブロックはトリガーされません。長い間...

79
jennas

タイムアウト間隔の変更は、ほぼ間違いなく、説明している問題に対する最善の解決策ではありません。代わりに、実際に必要なのは、HTTPクライアントがネットワークに到達できないように処理することです。

AFHTTPClientには、インターネット接続が失われたときに通知する組み込みのメカニズムが既にあります。-setReachabilityStatusChangeBlock:

低速ネットワークでは、リクエストに時間がかかる場合があります。遅い接続を処理する方法を知っているiOSを信頼し、それとまったく接続がないことの違いを伝える方が良いです。


このスレッドで言及されている他のアプローチを避けるべき理由についての私の推論を拡張するために、いくつかの考えがあります:

  • リクエストは、開始される前にキャンセルすることができます。要求をキューに登録しても、実際にいつ開始されるかは保証されません。
  • タイムアウト間隔は、長時間実行されるリクエスト、特にPOSTをキャンセルするべ​​きではありません。 100MBのビデオをダウンロードまたはアップロードしようとした場合を想像してください。リクエストが低速の3Gネットワ​​ークでできる限りうまく進んでいる場合、予想よりも少し時間がかかっているのに、なぜそれを不必要に停止するのでしょうか?
  • performSelector:afterDelay:...は、マルチスレッドアプリケーションでは危険です。これにより、あいまいでデバッグが困難な競合状態が発生します。
109
mattt

上記のmatttの答えを見ることを強くお勧めします-この答えは、彼が一般的に言及している問題に反するものではありませんが、元のポスターの質問では、到達可能性をチェックする方がはるかに適しています。

ただし、タイムアウトを設定したい場合は(performSelector:afterDelay:など、レゴが言及しているプルリクエストでは、これを行う方法をコメントの1つとして説明しています。

NSMutableURLRequest *request = [client requestWithMethod:@"GET" path:@"/" parameters:nil];
[request setTimeoutInterval:120];

AFHTTPRequestOperation *operation = [client HTTPRequestOperationWithRequest:request success:^{...} failure:^{...}];
[client enqueueHTTPRequestOperation:operation];

しかし、@ KCHarwoodが言及している警告を参照してくださいApple POST要求(iOS 6以降で修正されています) 。

@ChrisopherPickslayが指摘しているように、これは全体的なタイムアウトではなく、データの受信(または送信)間のタイムアウトです。全体的なタイムアウトを賢明に行う方法を知りません。 Apple setTimeoutIntervalのドキュメントには次のように書かれています:

秒単位のタイムアウト間隔。接続試行中に、要求がタイムアウト間隔よりも長い間アイドル状態のままである場合、要求はタイムアウトしたと見なされます。デフォルトのタイムアウト間隔は60秒です。

43
JosephH

RequestSerializer setTimeoutIntervalメソッドを使用してタイムアウト間隔を設定できます。AFHTTPRequestOperationManagerインスタンスからrequestSerializerを取得できます。

たとえば、25秒のタイムアウトでpostリクエストを実行するには:

    NSDictionary *params = @{@"par1": @"value1",
                         @"par2": @"value2"};

    AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];

    [manager.requestSerializer setTimeoutInterval:25];  //Time out after 25 seconds

    [manager POST:@"URL" parameters:params success:^(AFHTTPRequestOperation *operation, id responseObject) {

    //Success call back bock
    NSLog(@"Request completed with response: %@", responseObject);


    } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
     //Failure callback block. This block may be called due to time out or any other failure reason
    }];
26

現時点では、手動でパッチを適用する必要があると思います。

AFHTTPClientをサブクラス化し、変更しました

- (NSMutableURLRequest *)requestWithMethod:(NSString *)method path:(NSString *)path parameters:(NSDictionary *)parameters

追加する方法

[request setTimeoutInterval:10.0];

in AFHTTPClient.m line236。もちろんそれを設定できれば良いのですが、私が知る限り、現時点では不可能です。

7
Cornelius

最終的に判明非同期でそれを行う方法POSTリクエスト:

_- (void)timeout:(NSDictionary*)dict {
    NDLog(@"timeout");
    AFHTTPRequestOperation *operation = [dict objectForKey:@"operation"];
    if (operation) {
        [operation cancel];
    }
    [[AFNetworkActivityIndicatorManager sharedManager] decrementActivityCount];
    [self perform:[[dict objectForKey:@"selector"] pointerValue] on:[dict objectForKey:@"object"] with:nil];
}

- (void)perform:(SEL)selector on:(id)target with:(id)object {
    if (target && [target respondsToSelector:selector]) {
        [target performSelector:selector withObject:object];
    }
}

- (void)doStuffAndNotifyObject:(id)object withSelector:(SEL)selector {
    // AFHTTPRequestOperation asynchronous with selector                
    NSDictionary *params = [NSDictionary dictionaryWithObjectsAndKeys:
                            @"doStuff", @"task",
                            nil];

    AFHTTPClient *httpClient = [[AFHTTPClient alloc] initWithBaseURL:[NSURL URLWithString:baseURL]];

    NSMutableURLRequest *request = [httpClient requestWithMethod:@"POST" path:requestURL parameters:params];
    [httpClient release];

    AFHTTPRequestOperation *operation = [[[AFHTTPRequestOperation alloc] initWithRequest:request] autorelease];

    NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:
                          operation, @"operation", 
                          object, @"object", 
                          [NSValue valueWithPointer:selector], @"selector", 
                          nil];
    [self performSelector:@selector(timeout:) withObject:dict afterDelay:timeout];

    [operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {            
        [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(timeout:) object:dict];
        [[AFNetworkActivityIndicatorManager sharedManager] decrementActivityCount];
        [self perform:selector on:object with:[operation responseString]];
    }
    failure:^(AFHTTPRequestOperation *operation, NSError *error) {
        NDLog(@"fail! \nerror: %@", [error localizedDescription]);
        [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(timeout:) object:dict];
        [[AFNetworkActivityIndicatorManager sharedManager] decrementActivityCount];
        [self perform:selector on:object with:nil];
    }];

    NSOperationQueue *queue = [[[NSOperationQueue alloc] init] autorelease];
    [[AFNetworkActivityIndicatorManager sharedManager] incrementActivityCount];
    [queue addOperation:operation];
}
_

サーバーをsleep(aFewSeconds)にして、このコードをテストしました。

同期POSTリクエストを行う必要がある場合は、[〜#〜] not [〜#〜] use _[queue waitUntilAllOperationsAreFinished];_を使用してください。代わりに同じアプローチを使用してください非同期リクエストに関しては、セレクター引数で渡す関数がトリガーされるのを待ちます。

7
borisdiakur

他のユーザーの回答と、関連するプロジェクトの問題に関する@matttの提案に基づいて、AFHTTPClientをサブクラス化する場合の簡単な説明を次に示します。

@implementation SomeAPIClient // subclass of AFHTTPClient

// ...

- (NSMutableURLRequest *)requestWithMethod:(NSString *)method path:(NSString *)path parameters:(NSDictionary *)parameters {
  NSMutableURLRequest *request = [super requestWithMethod:method path:path parameters:parameters];
  [request setTimeoutInterval:120];
  return request;
}

- (NSMutableURLRequest *)multipartFormRequestWithMethod:(NSString *)method path:(NSString *)path parameters:(NSDictionary *)parameters constructingBodyWithBlock:(void (^)(id <AFMultipartFormData> formData))block {
  NSMutableURLRequest *request = [super requestWithMethod:method path:path parameters:parameters];
  [request setTimeoutInterval:120];
  return request;
}

@end

IOS 6で動作するようにテスト済み。

5
Gurpartap Singh

このようなタイマーでこれを行うことはできません:

.hファイル内

{
NSInteger time;
AFJSONRequestOperation *operation;
}

.mファイル内

-(void)AFNetworkingmethod{

    time = 0;

    NSTtimer *timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(startTimer:) userInfo:nil repeats:YES];
    [timer fire];


    operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
        [self operationDidFinishLoading:JSON];
    } failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
        [self operationDidFailWithError:error];
    }];
    [operation setJSONReadingOptions:NSJSONReadingMutableContainers];
    [operation start];
}

-(void)startTimer:(NSTimer *)someTimer{
    if (time == 15&&![operation isFinished]) {
        time = 0;
        [operation invalidate];
        [operation cancel];
        NSLog(@"Timeout");
        return;
    }
    ++time;
}
0
Ulaş Sancak

ここでの「タイムアウト」の定義には2つの異なる意味があります。

timeoutIntervalのようなタイムアウト

任意の時間間隔よりも長くアイドル状態になった(転送がなくなった)要求をドロップします。例:timeoutIntervalを10秒に設定し、リクエストを12:00:00に開始し、12:00:23までデータを転送する場合があります。その後、接続は12:00:33にタイムアウトします。このケースは、ここでのほぼすべての回答(JosephH、Mostafa Abdellateef、Cornelius、およびGurpartap Singhを含む)でカバーされています。

timeoutDeadlineのようなタイムアウト

後で任意に発生する期限に達したときに、要求をドロップします。例:deadlineを将来10秒に設定し、リクエストを12:00:00に開始すると、12:00:23までデータを転送しようとする場合がありますが、接続は12に早くタイムアウトします。 00:10。このケースは、borisdiakurによってカバーされています。

これを実装する方法を示したいdeadline in Swift(3 and 4)for AFNetworking 3.1。

let sessionManager = AFHTTPSessionManager(baseURL: baseURL)
let request = sessionManager.post(endPoint, parameters: parameters, progress: { ... }, success: { ... }, failure: { ... })
// timeout deadline at 10 seconds in the future
DispatchQueue.global().asyncAfter(deadline: .now() + 10.0) {
    request?.cancel()
}

そして、テスト可能な例を示すために、このコードは将来の0.0秒での即時タイムアウトのため、「成功」ではなく「失敗」を出力する必要があります。

let sessionManager = AFHTTPSessionManager(baseURL: URL(string: "https://example.com"))
sessionManager.responseSerializer = AFHTTPResponseSerializer()
let request = sessionManager.get("/", parameters: nil, progress: nil, success: { _ in
    print("success")
}, failure: { _ in
    print("failure")
})
// timeout deadline at 0 seconds in the future
DispatchQueue.global().asyncAfter(deadline: .now() + 0.0) {
    request?.cancel()
}
0
Cœur