Mój projekt korzysta z AFNetworking.
https://github.com/AFNetworking/AFNetworking
Jak skrócić limit czasu? W przypadku braku połączenia z Internetem blokada awarii nie jest wyzwalana przez około 2 minuty. Czekaj zbyt długo ...
Mój projekt korzysta z AFNetworking.
https://github.com/AFNetworking/AFNetworking
Jak skrócić limit czasu? W przypadku braku połączenia z Internetem blokada awarii nie jest wyzwalana przez około 2 minuty. Czekaj zbyt długo ...
Odpowiedzi:
Zmiana limitu czasu prawie na pewno nie jest najlepszym rozwiązaniem opisywanego problemu. Zamiast tego wydaje się, że tak naprawdę chcesz, aby klient HTTP poradził sobie z utratą dostępu do sieci, nie?
AFHTTPClient
już ma wbudowany mechanizm poinformować, gdy internet jest stracone, -setReachabilityStatusChangeBlock:
.
Żądania mogą zająć dużo czasu w powolnych sieciach. Lepiej zaufać systemowi iOS, aby wiedzieć, jak radzić sobie z powolnymi połączeniami i odróżnić to od braku połączenia.
Aby rozwinąć moje rozumowanie, dlaczego należy unikać innych podejść wspomnianych w tym wątku, oto kilka myśli:
performSelector:afterDelay:...
może być niebezpieczne w aplikacjach wielowątkowych. To otwiera się na niejasne i trudne do debugowania warunki wyścigu.Zdecydowanie polecam przyjrzenie się powyższej odpowiedzi Mattta - chociaż ta odpowiedź nie jest obarczona problemami, o których wspomina w ogóle, w przypadku oryginalnego pytania dotyczącego plakatów sprawdzenie osiągalności jest znacznie lepszym rozwiązaniem.
Jeśli jednak nadal chcesz ustawić limit czasu (bez wszystkich problemów związanych z performSelector:afterDelay:
itp., Wówczas żądanie ściągnięcia Lego wspomina o sposobie zrobienia tego jako jeden z komentarzy, po prostu wykonaj:
NSMutableURLRequest *request = [client requestWithMethod:@"GET" path:@"/" parameters:nil];
[request setTimeoutInterval:120];
AFHTTPRequestOperation *operation = [client HTTPRequestOperationWithRequest:request success:^{...} failure:^{...}];
[client enqueueHTTPRequestOperation:operation];
ale zobacz zastrzeżenie @KCHarwood wspomina, że wygląda na to, że Apple nie zezwala na zmianę tego dla żądań POST (co jest naprawione w iOS 6 i nowszych).
Jak wskazuje @ChrisopherPickslay, nie jest to ogólny limit czasu, jest to limit czasu między otrzymaniem (lub wysłaniem danych). Nie znam żadnego sposobu na rozsądne przekroczenie limitu czasu. Dokumentacja Apple dotycząca setTimeoutInterval mówi:
Limit czasu w sekundach. Jeśli podczas próby połączenia żądanie pozostaje bezczynne przez czas dłuższy niż limit czasu, uważa się, że upłynął limit czasu żądania. Domyślny limit czasu to 60 sekund.
timeoutInterval
jest licznikiem czasu bezczynności, a nie limitem czasu żądania. Więc musiałbyś otrzymywać żadne dane przez 120 sekund, aby powyższy kod wygasł. Jeśli dane powoli napływają, żądanie może trwać w nieskończoność.
Limit czasu można ustawić za pomocą metody requestSerializer setTimeoutInterval. RequestSerializer można pobrać z instancji AFHTTPRequestOperationManager.
Na przykład, aby wysłać żądanie wpisu z limitem czasu 25 sekund:
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
}];
Myślę, że w tej chwili musisz to załatać ręcznie.
Podklasuję AFHTTPClient i zmieniłem
- (NSMutableURLRequest *)requestWithMethod:(NSString *)method path:(NSString *)path parameters:(NSDictionary *)parameters
metoda poprzez dodanie
[request setTimeoutInterval:10.0];
w wierszu AFHTTPClient.m 236. Oczywiście byłoby dobrze, gdyby można to skonfigurować, ale z tego, co widzę, w tej chwili nie jest to możliwe.
Wreszcie dowiedziałem się, jak to zrobić za pomocą asynchronicznego żądania 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];
}
Przetestowałem ten kod, pozwalając mojemu serwerowi sleep(aFewSeconds)
.
Jeśli chcesz wykonać synchroniczne żądanie POST, NIE używaj [queue waitUntilAllOperationsAreFinished];
. Zamiast tego użyj tego samego podejścia, co w przypadku żądania asynchronicznego i poczekaj na wyzwolenie funkcji, którą przekazujesz w argumencie selektora.
W oparciu o odpowiedzi innych osób i sugestię @ mattt na temat powiązanych problemów z projektem, oto szybki numerek, jeśli tworzysz podklasy 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
Przetestowano pod kątem działania na iOS 6.
Czy nie możemy tego zrobić z takim timerem:
W pliku .h
{
NSInteger time;
AFJSONRequestOperation *operation;
}
W pliku .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;
}
Definicja „limitu czasu” ma tutaj dwa różne znaczenia.
timeoutInterval
Chcesz porzucić żądanie, gdy stanie się bezczynne (nie będzie więcej przesyłania) dłużej niż przez dowolny okres czasu. Przykład: ustawisz timeoutInterval
10 sekund, zaczynasz żądanie o 12:00:00, może przesłać część danych do 12:00:23, a następnie połączenie wygaśnie o 12:00:33. Ten przypadek jest objęty prawie wszystkimi odpowiedziami tutaj (w tym JosephH, Mostafa Abdellateef, Cornelius i Gurpartap Singh).
timeoutDeadline
Chcesz odrzucić wniosek, gdy nadejdzie termin przypadający w dowolnym momencie. Przykład: ustawiłeś deadline
na 10 sekund w przyszłości, zaczynasz żądanie o 12:00:00, może próbować przesłać niektóre dane do 12:00:23, ale połączenie wygaśnie wcześniej o 12:00:10. Ten przypadek pokrywa borisdiakur.
Chciałbym pokazać, jak wdrożyć ten termin w Swift (3 i 4) dla 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()
}
Aby dać testowalny przykład, ten kod powinien wypisać „niepowodzenie” zamiast „sukces” ze względu na natychmiastowy limit czasu wynoszący 0,0 sekundy w przyszłości:
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()
}
Zgadzam się z Mattem, nie powinieneś próbować zmieniać timeoutInterval. Ale nie powinieneś również polegać na sprawdzaniu osiągalności, aby zdecydować, czy zamierzasz nawiązać połączenie, nie wiesz, dopóki nie spróbujesz.
Jak stwierdzono w dokumencie Apple:
Zasadniczo nie należy używać krótkich przedziałów czasu, a zamiast tego należy zapewnić użytkownikowi łatwy sposób na anulowanie długotrwałej operacji. Aby uzyskać więcej informacji, przeczytaj „Projektowanie sieci w świecie rzeczywistym”.
performSelector:afterDelay:...
do ręcznego anulowania istniejących operacji. Zobacz moją odpowiedź, aby uzyskać więcej informacji.