281 lines
12 KiB
Mathematica
281 lines
12 KiB
Mathematica
|
// Copyright 2019 Google
|
||
|
//
|
||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
// you may not use this file except in compliance with the License.
|
||
|
// You may obtain a copy of the License at
|
||
|
//
|
||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||
|
//
|
||
|
// Unless required by applicable law or agreed to in writing, software
|
||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
// See the License for the specific language governing permissions and
|
||
|
// limitations under the License.
|
||
|
|
||
|
#import "FIRCLSFABNetworkClient.h"
|
||
|
|
||
|
#if FIRCLSURLSESSION_REQUIRED
|
||
|
#import "FIRCLSURLSession.h"
|
||
|
#endif
|
||
|
|
||
|
#import "FIRCLSNetworkResponseHandler.h"
|
||
|
|
||
|
static const float FIRCLSNetworkMinimumRetryJitter = 0.90f;
|
||
|
static const float FIRCLSNetworkMaximumRetryJitter = 1.10f;
|
||
|
const NSUInteger FIRCLSNetworkMaximumRetryCount = 10;
|
||
|
|
||
|
@interface FIRCLSFABNetworkClient () <NSURLSessionDelegate, NSURLSessionTaskDelegate>
|
||
|
|
||
|
@property(nonatomic, strong, readonly) NSURLSession *session;
|
||
|
|
||
|
@end
|
||
|
|
||
|
@implementation FIRCLSFABNetworkClient
|
||
|
|
||
|
- (instancetype)init {
|
||
|
return [self initWithQueue:nil];
|
||
|
}
|
||
|
|
||
|
- (instancetype)initWithQueue:(nullable NSOperationQueue *)operationQueue {
|
||
|
#if !FIRCLSURLSESSION_REQUIRED
|
||
|
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
|
||
|
#else
|
||
|
NSURLSessionConfiguration *config = [FIRCLSURLSessionConfiguration defaultSessionConfiguration];
|
||
|
#endif
|
||
|
return [self initWithSessionConfiguration:config queue:operationQueue];
|
||
|
}
|
||
|
|
||
|
- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)config
|
||
|
queue:(nullable NSOperationQueue *)operationQueue {
|
||
|
self = [super init];
|
||
|
if (!self) {
|
||
|
return nil;
|
||
|
}
|
||
|
|
||
|
#if !FIRCLSURLSESSION_REQUIRED
|
||
|
_session = [NSURLSession sessionWithConfiguration:config
|
||
|
delegate:self
|
||
|
delegateQueue:operationQueue];
|
||
|
#else
|
||
|
_session = [FIRCLSURLSession sessionWithConfiguration:config
|
||
|
delegate:self
|
||
|
delegateQueue:operationQueue];
|
||
|
#endif
|
||
|
if (!_session) {
|
||
|
return nil;
|
||
|
}
|
||
|
|
||
|
return self;
|
||
|
}
|
||
|
|
||
|
- (void)dealloc {
|
||
|
[_session finishTasksAndInvalidate];
|
||
|
}
|
||
|
|
||
|
#pragma mark - Delay Handling
|
||
|
- (double)randomDoubleWithMin:(double)min max:(double)max {
|
||
|
return min + ((max - min) * drand48());
|
||
|
}
|
||
|
|
||
|
- (double)generateRandomJitter {
|
||
|
return [self randomDoubleWithMin:FIRCLSNetworkMinimumRetryJitter
|
||
|
max:FIRCLSNetworkMaximumRetryJitter];
|
||
|
}
|
||
|
|
||
|
- (NSTimeInterval)computeDelayForResponse:(NSURLResponse *)response
|
||
|
withRetryCount:(NSUInteger)count {
|
||
|
NSTimeInterval initialValue = [FIRCLSNetworkResponseHandler retryValueForResponse:response];
|
||
|
|
||
|
// make sure count is > 0
|
||
|
count = MAX(count, 1);
|
||
|
// make sure initialValue is >2 for exponential backoff to work reasonably with low count numbers
|
||
|
initialValue = MAX(initialValue, 2.0);
|
||
|
|
||
|
const double jitter = [self generateRandomJitter];
|
||
|
|
||
|
return pow(initialValue, count) * jitter; // exponential backoff
|
||
|
}
|
||
|
|
||
|
- (void)runAfterRetryValueFromResponse:(NSURLResponse *)response
|
||
|
attempts:(NSUInteger)count
|
||
|
onQueue:(dispatch_queue_t)queue
|
||
|
block:(void (^)(void))block {
|
||
|
const NSTimeInterval delay = [self computeDelayForResponse:response withRetryCount:count];
|
||
|
|
||
|
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (uint64_t)(delay * NSEC_PER_SEC)), queue, block);
|
||
|
}
|
||
|
|
||
|
- (void)runAfterRetryValueFromResponse:(NSURLResponse *)response
|
||
|
attempts:(NSUInteger)count
|
||
|
block:(void (^)(void))block {
|
||
|
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
|
||
|
|
||
|
[self runAfterRetryValueFromResponse:response attempts:count onQueue:queue block:block];
|
||
|
}
|
||
|
|
||
|
#pragma mark - Tasks
|
||
|
|
||
|
- (void)startDataTaskWithRequest:(NSURLRequest *)request
|
||
|
retryLimit:(NSUInteger)retryLimit
|
||
|
tries:(NSUInteger)tries
|
||
|
completionHandler:(FIRCLSNetworkDataTaskCompletionHandlerBlock)completionHandler {
|
||
|
NSURLSessionTask *task = [self.session
|
||
|
dataTaskWithRequest:request
|
||
|
completionHandler:^(NSData *data, NSURLResponse *response, NSError *taskError) {
|
||
|
[FIRCLSNetworkResponseHandler
|
||
|
handleCompletedResponse:response
|
||
|
forOriginalRequest:request
|
||
|
error:taskError
|
||
|
block:^(BOOL retry, NSError *error) {
|
||
|
if (!retry) {
|
||
|
completionHandler(data, response, error);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (tries >= retryLimit) {
|
||
|
NSDictionary *userInfo = @{
|
||
|
@"retryLimit" : @(retryLimit),
|
||
|
NSURLErrorFailingURLStringErrorKey : request.URL
|
||
|
};
|
||
|
completionHandler(
|
||
|
nil, nil,
|
||
|
[NSError
|
||
|
errorWithDomain:FIRCLSNetworkErrorDomain
|
||
|
code:FIRCLSNetworkErrorMaximumAttemptsReached
|
||
|
userInfo:userInfo]);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
[self
|
||
|
runAfterRetryValueFromResponse:response
|
||
|
attempts:tries
|
||
|
block:^{
|
||
|
[self
|
||
|
startDataTaskWithRequest:
|
||
|
request
|
||
|
retryLimit:
|
||
|
retryLimit
|
||
|
tries:
|
||
|
(tries +
|
||
|
1)
|
||
|
completionHandler:
|
||
|
completionHandler];
|
||
|
}];
|
||
|
}];
|
||
|
}];
|
||
|
|
||
|
[task resume];
|
||
|
|
||
|
if (!task) {
|
||
|
completionHandler(nil, nil,
|
||
|
[NSError errorWithDomain:FIRCLSNetworkErrorDomain
|
||
|
code:FIRCLSNetworkErrorFailedToStartOperation
|
||
|
userInfo:nil]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
- (void)startDataTaskWithRequest:(NSURLRequest *)request
|
||
|
retryLimit:(NSUInteger)retryLimit
|
||
|
completionHandler:(FIRCLSNetworkDataTaskCompletionHandlerBlock)completionHandler {
|
||
|
[self startDataTaskWithRequest:request
|
||
|
retryLimit:retryLimit
|
||
|
tries:0
|
||
|
completionHandler:completionHandler];
|
||
|
}
|
||
|
|
||
|
- (void)startDataTaskWithRequest:(NSURLRequest *)request
|
||
|
completionHandler:(FIRCLSNetworkDataTaskCompletionHandlerBlock)completionHandler {
|
||
|
[self startDataTaskWithRequest:request
|
||
|
retryLimit:FIRCLSNetworkMaximumRetryCount
|
||
|
completionHandler:completionHandler];
|
||
|
}
|
||
|
|
||
|
- (void)startDownloadTaskWithRequest:(NSURLRequest *)request
|
||
|
retryLimit:(NSUInteger)retryLimit
|
||
|
tries:(NSUInteger)tries
|
||
|
completionHandler:
|
||
|
(FIRCLSNetworkDownloadTaskCompletionHandlerBlock)completionHandler {
|
||
|
NSURLSessionTask *task = [self.session
|
||
|
downloadTaskWithRequest:request
|
||
|
completionHandler:^(NSURL *location, NSURLResponse *response, NSError *taskError) {
|
||
|
[FIRCLSNetworkResponseHandler
|
||
|
handleCompletedResponse:response
|
||
|
forOriginalRequest:request
|
||
|
error:taskError
|
||
|
block:^(BOOL retry, NSError *error) {
|
||
|
if (!retry) {
|
||
|
completionHandler(location, response, error);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (tries >= retryLimit) {
|
||
|
NSDictionary *userInfo = @{
|
||
|
@"retryLimit" : @(retryLimit),
|
||
|
NSURLErrorFailingURLStringErrorKey : request.URL
|
||
|
};
|
||
|
completionHandler(
|
||
|
nil, nil,
|
||
|
[NSError
|
||
|
errorWithDomain:FIRCLSNetworkErrorDomain
|
||
|
code:
|
||
|
FIRCLSNetworkErrorMaximumAttemptsReached
|
||
|
userInfo:userInfo]);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
[self
|
||
|
runAfterRetryValueFromResponse:response
|
||
|
attempts:tries
|
||
|
block:^{
|
||
|
[self
|
||
|
startDownloadTaskWithRequest:
|
||
|
request
|
||
|
retryLimit:
|
||
|
retryLimit
|
||
|
tries:
|
||
|
(tries +
|
||
|
1)
|
||
|
completionHandler:
|
||
|
completionHandler];
|
||
|
}];
|
||
|
}];
|
||
|
}];
|
||
|
|
||
|
[task resume];
|
||
|
|
||
|
if (!task) {
|
||
|
completionHandler(nil, nil,
|
||
|
[NSError errorWithDomain:FIRCLSNetworkErrorDomain
|
||
|
code:FIRCLSNetworkErrorFailedToStartOperation
|
||
|
userInfo:nil]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
- (void)startDownloadTaskWithRequest:(NSURLRequest *)request
|
||
|
retryLimit:(NSUInteger)retryLimit
|
||
|
completionHandler:
|
||
|
(FIRCLSNetworkDownloadTaskCompletionHandlerBlock)completionHandler {
|
||
|
[self startDownloadTaskWithRequest:request
|
||
|
retryLimit:retryLimit
|
||
|
tries:0
|
||
|
completionHandler:completionHandler];
|
||
|
}
|
||
|
|
||
|
- (void)startDownloadTaskWithRequest:(NSURLRequest *)request
|
||
|
completionHandler:
|
||
|
(FIRCLSNetworkDownloadTaskCompletionHandlerBlock)completionHandler {
|
||
|
[self startDownloadTaskWithRequest:request
|
||
|
retryLimit:FIRCLSNetworkMaximumRetryCount
|
||
|
completionHandler:completionHandler];
|
||
|
}
|
||
|
|
||
|
- (void)invalidateAndCancel {
|
||
|
[self.session invalidateAndCancel];
|
||
|
}
|
||
|
|
||
|
#pragma mark - NSURLSession Delegate
|
||
|
- (void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(NSError *)error {
|
||
|
}
|
||
|
|
||
|
@end
|