Cách đặt thời gian chờ với AFNetworking


79

Dự án của tôi đang sử dụng AFNetworking.

https://github.com/AFNetworking/AFNetworking

Làm cách nào để quay số thời gian chờ? Atm không có kết nối internet, khối lỗi không được kích hoạt trong khoảng 2 phút. Waay to long ....


2
Tôi thực sự khuyên bạn không nên sử dụng bất kỳ giải pháp nào cố gắng ghi đè khoảng thời gian chờ, đặc biệt là những giải pháp sử dụng performSelector:afterDelay:...để hủy thủ công các hoạt động hiện có. Vui lòng xem câu trả lời của tôi để biết thêm chi tiết.
mattt

Câu trả lời:


110

Thay đổi khoảng thời gian chờ gần như chắc chắn không phải là giải pháp tốt nhất cho vấn đề bạn đang mô tả. Thay vào đó, có vẻ như những gì bạn thực sự muốn là máy khách HTTP xử lý mạng trở nên không thể truy cập được, phải không?

AFHTTPClientđã có một cơ chế tích hợp để cho bạn biết khi mất kết nối internet , -setReachabilityStatusChangeBlock:.

Yêu cầu có thể mất nhiều thời gian trên mạng chậm. Tốt hơn hết bạn nên tin tưởng iOS để biết cách xử lý kết nối chậm và phân biệt giữa điều đó và không có kết nối.


Để mở rộng lý luận của tôi về lý do tại sao nên tránh các cách tiếp cận khác được đề cập trong chủ đề này, đây là một vài suy nghĩ:

  • Yêu cầu có thể bị hủy trước khi bắt đầu. Việc xếp hàng một yêu cầu không đảm bảo về thời điểm nó thực sự bắt đầu.
  • Khoảng thời gian chờ không được hủy các yêu cầu đang chạy dài — đặc biệt là POST. Hãy tưởng tượng nếu bạn đang cố tải xuống hoặc tải lên một video 100MB. Nếu yêu cầu diễn ra tốt nhất có thể trên mạng 3G chậm, tại sao bạn lại không cần dừng nó lại nếu nó mất nhiều thời gian hơn dự kiến?
  • Việc làm này performSelector:afterDelay:...có thể nguy hiểm trong các ứng dụng đa luồng. Điều này mở ra cho bản thân những điều kiện cuộc đua khó hiểu và khó gỡ lỗi.

Tôi tin rằng đó là vì câu hỏi là, mặc dù nó có tiêu đề, là làm thế nào để thất bại nhanh nhất có thể trong trường hợp "Không có internet". Cách chính xác để làm điều đó là sử dụng khả năng tiếp cận. Sử dụng thời gian chờ không hoạt động hoặc có khả năng cao tạo ra các lỗi khó nhận thấy / sửa chữa.
Mihai Timar 14/09/13

2
@mattt mặc dù tôi đồng ý với cách tiếp cận của bạn, nhưng tôi đang đấu tranh với sự cố kết nối mà tôi thực sự cần chức năng hết giờ. Vấn đề là - tôi đang tương tác với một thiết bị có "điểm phát sóng WiFi". Khi kết nối với "mạng WiFi" này, về khả năng kết nối, tôi không kết nối được mặc dù tôi có thể thực hiện các yêu cầu với thiết bị. Mặt khác, tôi muốn xác định xem bản thân thiết bị có thể truy cập được hay không. Có suy nghĩ gì không? Cảm ơn
Stavash

42
điểm tốt nhưng không trả lời câu hỏi về cách thiết lập một thời gian chờ
Max MacLeod

3
Tại AFNetworking Reference, "Khả năng tiếp cận mạng là một công cụ chẩn đoán có thể được sử dụng để hiểu lý do tại sao một yêu cầu có thể không thành công. Nó không nên được sử dụng để xác định có thực hiện một yêu cầu hay không." tại phần 'setReachabilityStatusChangeBlock'. Vì vậy, tôi nghĩ rằng 'setReachabilityStatusChangeBlock' không phải là giải pháp ...
LKM

1
Tôi hiểu tại sao chúng ta nên suy nghĩ kỹ về việc đặt thời gian chờ theo cách thủ công, nhưng câu trả lời được chấp nhận này không trả lời được câu hỏi. Ứng dụng của tôi cần biết càng sớm càng tốt khi mất kết nối Internet. Sử dụng AFNetworking, ReachabilityManager không phát hiện ra trường hợp thiết bị được kết nối với điểm phát Wifi, nhưng điểm phát đó lại mất kết nối Internet (thường mất vài phút để phát hiện). Vì vậy, đưa ra một yêu cầu tới Google với thời gian chờ ~ 5 giây có vẻ là lựa chọn tốt nhất của tôi trong trường hợp đó. Trừ khi có điều gì đó mà tôi đang thiếu?
Tanner Semerad

44

Tôi thực sự khuyên bạn nên xem câu trả lời của mattt ở trên - mặc dù câu trả lời này không liên quan đến các vấn đề mà anh ấy đề cập nói chung, đối với câu hỏi áp phích ban đầu, kiểm tra khả năng tiếp cận là phù hợp hơn nhiều.

Tuy nhiên, nếu bạn vẫn muốn đặt thời gian chờ (mà không có tất cả các vấn đề vốn có trong performSelector:afterDelay:v.v., thì yêu cầu kéo mà Lego đề cập mô tả một cách để thực hiện điều này như một trong các nhận xét, bạn chỉ cần làm:

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

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

nhưng hãy xem cảnh báo @KCHarwood đề cập rằng có vẻ như Apple không cho phép thay đổi điều này đối với các yêu cầu POST (đã được khắc phục trong iOS 6 trở lên).

Như @ChrisopherPickslay đã chỉ ra, đây không phải là thời gian chờ tổng thể mà là thời gian chờ giữa việc nhận (hoặc gửi dữ liệu). Tôi không biết bất kỳ cách nào để thực hiện thời gian chờ tổng thể một cách hợp lý. Tài liệu của Apple cho setTimeoutInterval cho biết:

Khoảng thời gian chờ, tính bằng giây. Nếu trong khi cố gắng kết nối, yêu cầu vẫn ở chế độ chờ lâu hơn khoảng thời gian chờ, yêu cầu được coi là đã hết thời gian chờ. Khoảng thời gian chờ mặc định là 60 giây.


Có, tôi đã dùng thử và nó không hoạt động khi thực hiện yêu cầu ĐĂNG.
borisdiakur

7
sidenote: của Apple cố định này trong iOS 6
Raptor

2
Điều này không làm những gì bạn đề nghị nó làm. timeoutIntervallà bộ hẹn giờ nhàn rỗi, không phải thời gian chờ yêu cầu. Vì vậy, bạn sẽ không nhận được dữ liệu nào trong 120 giây để mã trên hết thời gian chờ. Nếu dữ liệu từ từ chảy vào, yêu cầu có thể tiếp tục vô thời hạn.
Christopher Pickslay

Đó là một điểm công bằng mà tôi không thực sự nói nhiều về cách nó hoạt động - tôi hy vọng đã thêm thông tin này ngay bây giờ, cảm ơn!
JosephH


26

Bạn có thể đặt khoảng thời gian chờ thông qua phương thức requestSerializer setTimeoutInterval.Bạn có thể lấy requestSerializer từ phiên bản AFHTTPRequestOperationManager.

Ví dụ để thực hiện một yêu cầu đăng bài với thời gian chờ là 25 giây:

    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
    }];

7

Tôi nghĩ bạn phải vá lỗi đó bằng tay vào lúc này.

Tôi đang phân loại AFHTTPClient và đã thay đổi

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

phương pháp bằng cách thêm

[request setTimeoutInterval:10.0];

trong AFHTTPClient.m dòng 236. Tất nhiên sẽ rất tốt nếu điều đó có thể được định cấu hình, nhưng theo tôi thấy điều đó là không thể vào lúc này.


7
Một điều khác cần xem xét là Apple ghi đè thời gian chờ cho một ĐĂNG. Tôi nghĩ tự động một cái gì đó như 4 phút, và bạn KHÔNG THỂ thay đổi nó.
kcharwood

Tôi cũng thấy nó. Tại sao nó như vậy? Việc bắt người dùng đợi 4 phút trước khi kết nối bị lỗi là không tốt.
slatvick

2
Để thêm vào phản hồi @KCHarwood. Kể từ iOS 6, apple không ghi đè thời gian chờ của bài đăng. Điều này đã được sửa trong iOS 6.
ADAM

7

Cuối cùng đã tìm ra cách thực hiện với yêu cầu POST không đồng bộ:

- (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];
}

Tôi đã thử nghiệm mã này bằng cách cho phép máy chủ của tôi sleep(aFewSeconds).

Nếu bạn cần thực hiện yêu cầu ĐĂNG đồng bộ, KHÔNG sử dụng [queue waitUntilAllOperationsAreFinished];. Thay vào đó, hãy sử dụng phương pháp tương tự như đối với yêu cầu không đồng bộ và đợi hàm được kích hoạt mà bạn chuyển vào đối số bộ chọn.


18
Không không không không, vui lòng không sử dụng nó trong một ứng dụng thực tế. Những gì mã của bạn thực sự làm là hủy yêu cầu sau một khoảng thời gian bắt đầu khi hoạt động được tạo, chứ không phải khi nó bắt đầu . Điều này có thể khiến yêu cầu bị hủy trước khi bắt đầu.
mattt

5
@mattt Vui lòng cung cấp mẫu mã hoạt động. Trên thực tế, những gì bạn mô tả là chính xác những gì tôi muốn xảy ra: Tôi muốn khoảng thời gian bắt đầu tích tắc ngay khi tôi tạo thao tác.
borisdiakur

Cảm ơn Lego! Tôi đang sử dụng AFNetworking và ngẫu nhiên khoảng 10% thời gian hoạt động của tôi từ AFNetworking không bao giờ gọi các khối thành công hoặc thất bại [máy chủ ở Hoa Kỳ, người dùng thử nghiệm ở Trung Quốc]. Đây là một vấn đề lớn đối với tôi vì tôi đang cố tình chặn các phần của giao diện người dùng trong khi các yêu cầu đó đang được xử lý để ngăn người dùng gửi quá nhiều yêu cầu cùng một lúc. Cuối cùng, tôi đã triển khai một phiên bản dựa trên giải pháp này, chuyển một khối hoàn thành dưới dạng tham số thông qua trình thực hiện withdelay và đảm bảo rằng khối đó được thực thi if! Operation.isFinishing - Mattt: Cảm ơn bạn đã sử dụng AFNetworking!
Corey

5

Dựa trên câu trả lời của những người khác và đề xuất của @ mattt về các vấn đề liên quan của dự án, đây là một tài liệu đăng ký nhanh nếu bạn đang xếp lớp con 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

Đã thử nghiệm hoạt động trên iOS 6.


0

Chúng ta không thể làm điều này với một bộ đếm thời gian như thế này:

Trong tệp .h

{
NSInteger time;
AFJSONRequestOperation *operation;
}

Trong tệp .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

Có hai ý nghĩa khác nhau về định nghĩa "thời gian chờ" ở đây.

Hết giờ như trong timeoutInterval

Bạn muốn hủy một yêu cầu khi nó không hoạt động (không chuyển nữa) lâu hơn một khoảng thời gian tùy ý. Ví dụ: bạn đặt timeoutIntervalthành 10 giây, bạn bắt đầu yêu cầu của mình lúc 12:00:00, nó có thể chuyển một số dữ liệu cho đến 12:00:23, sau đó kết nối sẽ hết lúc 12:00:33. Trường hợp này được đề cập bởi hầu hết các câu trả lời ở đây (bao gồm JosephH, Mostafa Abdionaryef, Cornelius và Gurpartap Singh).

Hết giờ như trong timeoutDeadline

Bạn muốn bỏ một yêu cầu khi nó đến thời hạn tùy ý xảy ra sau đó. Ví dụ: bạn đặt deadlinethành 10 giây trong tương lai, bạn bắt đầu yêu cầu của mình lúc 12:00:00, nó có thể cố gắng chuyển một số dữ liệu cho đến 12:00:23, nhưng kết nối sẽ hết thời gian sớm hơn lúc 12:00:10. Trường hợp này được bảo hiểm bởi borisdiakur.

Tôi muốn chỉ ra cách thực hiện thời hạn này trong Swift (3 và 4) cho 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()
}

Và để đưa ra một ví dụ có thể kiểm tra, mã này sẽ in "thất bại" thay vì "thành công" vì hết thời gian chờ ngay lập tức là 0,0 giây trong tương lai:

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()
}

-2

Đồng ý với Matt, bạn không nên thử thay đổi timeoutInterval. Nhưng bạn cũng không nên dựa vào kiểm tra khả năng kết nối để quyết định thời tiết bạn sẽ thực hiện kết nối, bạn không biết cho đến khi bạn thử.

Như tài liệu của Apple đã nêu:

Theo nguyên tắc chung, bạn không nên sử dụng các khoảng thời gian chờ ngắn và thay vào đó, nên cung cấp một cách dễ dàng để người dùng hủy một hoạt động đang chạy trong thời gian dài. Để biết thêm thông tin, hãy đọc “Thiết kế cho các mạng trong thế giới thực”.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.