AFNetworking でタイムアウトを設定する方法
-
25-10-2019 - |
質問
私のプロジェクトはAFNetworkingを使用しています。
https://github.com/AFNetworking/AFNetworking
タイムアウトをダイヤルダウンするにはどうすればよいですか?インターネットに接続されていない ATM では、約 2 分間ほど失敗ブロックがトリガーされません。長いですね……。
解決
タイムアウト間隔を変更することは、ほぼ確実に、あなたが説明している問題に対する最善の解決策ではありません。むしろ、実際に必要なのは、HTTP クライアントがネットワークに到達できなくなった場合に処理することのようです。
AFHTTPClient
インターネット接続が切断された場合にそれを通知するメカニズムがすでに組み込まれています。 -setReachabilityStatusChangeBlock:
.
低速なネットワークではリクエストに時間がかかることがあります。iOS が遅い接続に対処する方法を知っており、接続がまったくない場合との違いを認識していると信頼する方が良いでしょう。
このスレッドで言及されている他のアプローチを避けるべき理由について私の推論を拡張するために、以下にいくつかの考えを示します。
- リクエストは開始前でもキャンセルできます。リクエストをキューに入れても、実際にいつ開始されるかは保証されません。
- タイムアウト間隔によって、長時間実行されるリクエスト (特に POST) がキャンセルされるべきではありません。100MB のビデオをダウンロードまたはアップロードしようとしていると想像してください。遅い 3G ネットワーク上でリクエストが可能な限り順調に進んでいるのに、予想より少し時間がかかっている場合、なぜ不必要にリクエストを停止するのでしょうか?
- やってる
performSelector:afterDelay:...
マルチスレッド アプリケーションでは危険な場合があります。これにより、あいまいでデバッグが困難な競合状態にさらされることになります。
他のヒント
上記の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が指摘しているように、これは全体的なタイムアウトではなく、受信(またはデータの送信)の間のタイムアウトです。全体的なタイムアウトを賢明に行う方法はわかりません。 SettimeOutIntervalのAppleドキュメントは次のように述べています。
タイムアウト間隔、秒単位。接続試行中に、リクエストがタイムアウト間隔よりも長くアイドル状態のままである場合、リクエストはタイムアウトしたと見なされます。デフォルトのタイムアウト間隔は60秒です。
RequestSerializer SetimeOutIntervalメソッドを使用してタイムアウト間隔を設定できます。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
}];
現時点では手動でパッチを当てなければならないと思います。
私はafhttpclientをサブクラス化しており、変更しました
- (NSMutableURLRequest *)requestWithMethod:(NSString *)method path:(NSString *)path parameters:(NSDictionary *)parameters
追加による方法
[request setTimeoutInterval:10.0];
の afhttpclient.m 236行目。もちろん、それを構成できれば良いでしょうが、私が見る限り、それは現時点では不可能です。
最後に発見されました 非同期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リクエストを行う必要がある場合は、 いいえ 使用する [queue waitUntilAllOperationsAreFinished];
. 。代わりに、非同期要求と同じアプローチを使用し、セレクター引数で渡す関数がトリガーされるのを待ちます。
他の人の回答と関連するプロジェクトの問題に関する @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で動作するようにテストされました。
このようなタイマーでこれを行うことはできません:
.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;
}
ここでの「タイムアウト」の定義には 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でカバーされています。
これを実装する方法を示したいと思います 締め切り AFNetworking 3.1 の Swift (3 および 4)。
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()
}
マットに同意してください、TimeoutIntervalを変更してみてはいけません。しかし、あなたはあなたが接続をするつもりの天気を決めるために到達可能性チェックに頼ってはいけません、あなたは試してみるまで知らないでしょう。
Appleドキュメントが述べているように:
一般的なルールとして、短いタイムアウト間隔を使用しないでください。代わりに、ユーザーが長期にわたる操作をキャンセルする簡単な方法を提供する必要があります。詳細については、「現実世界のネットワークの設計」をお読みください。