修改pods

This commit is contained in:
2025-09-20 17:13:38 +08:00
parent 7787b3ee30
commit 28ff2b0264
5251 changed files with 345029 additions and 285168 deletions

View File

@@ -97,41 +97,25 @@ NS_ASSUME_NONNULL_END
#define GTM_TARGET_SUPPORTS_APP_TRANSPORT_SECURITY 1
#endif
#if ((defined(TARGET_OS_MACCATALYST) && TARGET_OS_MACCATALYST) || \
(TARGET_OS_OSX && defined(__MAC_10_15) && __MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_10_15) || \
(TARGET_OS_IOS && defined(__IPHONE_13_0) && \
__IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_13_0) || \
(TARGET_OS_WATCH && defined(__WATCHOS_6_0) && \
__WATCH_OS_VERSION_MIN_REQUIRED >= __WATCHOS_6_0) || \
(TARGET_OS_TV && defined(__TVOS_13_0) && __TVOS_VERSION_MIN_REQUIRED >= __TVOS_13_0) || \
(defined(TARGET_OS_VISION) && TARGET_OS_VISION))
#if TARGET_OS_IOS
#if defined(__IPHONE_13_0) && __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_13_0
#define GTM_SDK_REQUIRES_TLSMINIMUMSUPPORTEDPROTOCOLVERSION 1
#define GTM_SDK_SUPPORTS_TLSMINIMUMSUPPORTEDPROTOCOLVERSION 1
#elif ((TARGET_OS_OSX && defined(__MAC_10_15) && __MAC_OS_X_VERSION_MAX_ALLOWED >= __MAC_10_15) || \
(TARGET_OS_IOS && defined(__IPHONE_13_0) && \
__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0) || \
(TARGET_OS_WATCH && defined(__WATCHOS_6_0) && \
__WATCH_OS_VERSION_MAX_ALLOWED >= __WATCHOS_6_0) || \
(TARGET_OS_TV && defined(__TVOS_13_0) && __TVOS_VERSION_MAX_ALLOWED >= __TVOS_13_0))
#define GTM_SDK_REQUIRES_TLSMINIMUMSUPPORTEDPROTOCOLVERSION 0
#define GTM_SDK_SUPPORTS_TLSMINIMUMSUPPORTEDPROTOCOLVERSION 1
#else
#define GTM_SDK_REQUIRES_TLSMINIMUMSUPPORTEDPROTOCOLVERSION 0
#define GTM_SDK_SUPPORTS_TLSMINIMUMSUPPORTEDPROTOCOLVERSION 0
#endif
#else // Not iOS
#define GTM_SDK_REQUIRES_TLSMINIMUMSUPPORTEDPROTOCOLVERSION 1
#endif
#if ((defined(TARGET_OS_MACCATALYST) && TARGET_OS_MACCATALYST) || \
(TARGET_OS_OSX && defined(__MAC_10_15) && __MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_10_15) || \
(TARGET_OS_IOS && defined(__IPHONE_13_0) && \
__IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_13_0) || \
(TARGET_OS_WATCH && defined(__WATCHOS_6_0) && \
__WATCH_OS_VERSION_MIN_REQUIRED >= __WATCHOS_6_0) || \
(TARGET_OS_TV && defined(__TVOS_13_0) && __TVOS_VERSION_MIN_REQUIRED >= __TVOS_13_0) || \
(defined(TARGET_OS_VISION) && TARGET_OS_VISION))
#if TARGET_OS_IOS
#if defined(__IPHONE_13_0) && __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_13_0
#define GTM_SDK_REQUIRES_SECTRUSTEVALUATEWITHERROR 1
#else
#define GTM_SDK_REQUIRES_SECTRUSTEVALUATEWITHERROR 0
#endif
#else // Not iOS
#define GTM_SDK_REQUIRES_SECTRUSTEVALUATEWITHERROR 1
#endif
#if __has_attribute(swift_async)
// Once Clang 13/Xcode 13 can be assumed, can switch to NS_SWIFT_DISABLE_ASYNC.
@@ -140,6 +124,21 @@ NS_ASSUME_NONNULL_END
#define GTM_SWIFT_DISABLE_ASYNC
#endif
// Internal tracking of the state within the `-beginFetch...` flow.
typedef NS_ENUM(NSUInteger, GTMSessionFetcherStartingState) {
// Not in any explcit part of the startup flow or about the re-enter the flow.
kStartingStateNone = 0,
// Parts of startup that can result in the fetcher completing startup at some later time.
kStartingStateServiceDelayed,
kStartingStateCalculatingUA,
kStartingStateAuthorizing,
kStartingStateApplyingDecorators,
// State while actively in `-beginFetchMayDelay:mayAuthorize:mayDecorate:`.
kStartingStateStartingUp,
};
@interface GTMSessionFetcher ()
@property(atomic, strong, readwrite, nullable) NSData *downloadedData;
@@ -256,6 +255,9 @@ static GTMSessionFetcherTestBlock _Nullable gGlobalTestBlock;
NSDate *_initialRequestDate; // date of first request to the target server (ignoring auth)
BOOL _hasAttemptedAuthRefresh; // accessed only in shouldRetryNowForStatus:
GTMSessionFetcherStartingState _startingState;
NSUInteger _pendingNotifications;
NSString *_comment; // comment for log
NSString *_log;
#if !STRIP_GTM_FETCH_LOGGING
@@ -497,14 +499,68 @@ static GTMSessionFetcherTestBlock _Nullable gGlobalTestBlock;
[self beginFetchWithCompletionHandler:handler];
}
// Helper to enter a new StartingState. If the fetch has already been stopped, then it will trigger
// a needed callback instead of setting the state. It returns YES/NO based on if starting up of the
// fetch should continue.
- (BOOL)startingState:(GTMSessionFetcherStartingState)newState __attribute__((objc_direct)) {
BOOL stopped = NO;
@synchronized(self) {
GTMSessionMonitorSynchronized(self);
GTMSESSION_ASSERT_DEBUG(
(_startingState == kStartingStateNone || _startingState == kStartingStateStartingUp),
@"Unexpected starting state: %lu", (unsigned long)_startingState);
if (_userStoppedFetching) {
stopped = YES;
} else {
_startingState = newState;
}
}
if (stopped) {
// We end up here if someone calls `stopFetching` from another thread/queue while
// the fetch was being started up, so while `stopFetching` did the needed shutdown
// we have to ensure the requested callback was triggered.
if (self.stopFetchingTriggersCompletionHandler) {
NSError *error = [NSError errorWithDomain:kGTMSessionFetcherErrorDomain
code:GTMSessionFetcherErrorUserCancelled
userInfo:nil];
[self failToBeginFetchWithError:error];
} else {
// In the edge case where the fetch was stopped before it even began, the call backs still
// need to get cleared as at least the completion will have been just set in calling
// beginFetch...
[self stopFetchReleasingCallbacks:YES];
}
return NO; // Caller to stop.
}
return YES;
}
// This is a private callback from the service to restart the fetcher after it was delayed
// due to the per host throttling.
- (void)serviceRestartingFetcher {
// Reset the starting state since the fetch is getting restarted.
@synchronized(self) {
GTMSessionMonitorSynchronized(self);
_startingState = kStartingStateNone;
}
[self beginFetchMayDelay:NO mayAuthorize:YES mayDecorate:YES];
}
- (void)beginFetchMayDelay:(BOOL)mayDelay
mayAuthorize:(BOOL)mayAuthorize
mayDecorate:(BOOL)mayDecorate {
// This is the internal entry point for re-starting fetches.
GTMSessionCheckNotSynchronized(self);
NSMutableURLRequest *fetchRequest =
_request; // The request property is now externally immutable.
if (![self startingState:kStartingStateStartingUp]) {
return;
}
// The request property is now externally immutable.
NSMutableURLRequest *fetchRequest = _request;
NSURL *fetchRequestURL = fetchRequest.URL;
NSString *priorSessionIdentifier = self.sessionIdentifier;
@@ -643,7 +699,7 @@ static GTMSessionFetcherTestBlock _Nullable gGlobalTestBlock;
return;
}
} // !isSecure
} // (requestURL != nil) && !isDataRequest
} // (requestURL != nil) && !isDataRequest
if (self.cookieStorage == nil) {
self.cookieStorage = [[self class] staticCookieStorage];
@@ -702,17 +758,43 @@ static GTMSessionFetcherTestBlock _Nullable gGlobalTestBlock;
mayDelay = NO;
}
if (mayDelay && _service) {
// Set the delayed state so there can't be a race between it getting queued in the
// `fetcherShouldBeginFetching:` call and some other thread completing a different fetch and
// thus starting this one. If we were trying to set the state based on the return result, there
// would be a small window for that race.
if (![self startingState:kStartingStateServiceDelayed]) {
return;
}
BOOL shouldFetchNow = [_service fetcherShouldBeginFetching:self];
if (!shouldFetchNow) {
// The fetch is deferred, but will happen later.
//
// If this session is held by the fetcher service, clear the session now so that we don't
// assume it's still valid after the fetcher is restarted.
//
// NOTE: In hindsight, this could be a race, some other thread could be starting it while
// doing these checks/work, but it also doesn't seem safe to put the whole block in a
// since `@synchronized(self)` block.
if (self.canShareSession) {
self.session = nil;
}
return;
}
@synchronized(self) {
GTMSessionMonitorSynchronized(self);
// If a `-stopFetching` came in while the service check was made, then the side effect of the
// state setting caused the handler (if needed) to already be made, so there we want to just
// exit and not continue the fetch.
if (_userStoppedFetching) {
return;
}
// Per comment above, correct state since it wasn't delayed, go back to startup state.
_startingState = kStartingStateStartingUp;
}
}
if ([fetchRequest valueForHTTPHeaderField:@"User-Agent"] == nil) {
@@ -725,7 +807,6 @@ static GTMSessionFetcherTestBlock _Nullable gGlobalTestBlock;
// The User-Agent is not cached in memory. Fetch it asynchronously.
[self updateUserAgentAsynchronouslyForRequest:fetchRequest
userAgentProvider:userAgentProvider
mayDelay:mayDelay
mayAuthorize:mayAuthorize
mayDecorate:mayDecorate];
// This method can't continue until the User-Agent header is fetched. The above
@@ -808,7 +889,16 @@ static GTMSessionFetcherTestBlock _Nullable gGlobalTestBlock;
}
}
// Even though the StartingUp state was entered at the start of the function, we re-enter it here
// in case a call to `-stopFetcher` comes in on another thread while the above code was running,
// this lets
// `-startingState:` do the needed checks/callbacks/cleanups without repeating them here.
if (![self startingState:kStartingStateStartingUp]) {
return;
}
// finally, start the connection
NSURLSessionTask *newSessionTask;
BOOL needsDataAccumulator = NO;
if (_downloadResumeData) {
@@ -906,10 +996,12 @@ static GTMSessionFetcherTestBlock _Nullable gGlobalTestBlock;
// UIApplication on the main thread.
UIBackgroundTaskIdentifier localTaskID;
@synchronized(self) {
GTMSessionMonitorSynchronized(self);
localTaskID = guardedTaskID;
}
if (localTaskID != UIBackgroundTaskInvalid) {
@synchronized(self) {
GTMSessionMonitorSynchronized(self);
if (localTaskID == self.backgroundTaskIdentifier) {
self.backgroundTaskIdentifier = UIBackgroundTaskInvalid;
}
@@ -918,6 +1010,7 @@ static GTMSessionFetcherTestBlock _Nullable gGlobalTestBlock;
}
}];
@synchronized(self) {
GTMSessionMonitorSynchronized(self);
guardedTaskID = returnedTaskID;
self.backgroundTaskIdentifier = returnedTaskID;
}
@@ -937,9 +1030,7 @@ static GTMSessionFetcherTestBlock _Nullable gGlobalTestBlock;
[self setStopNotificationNeeded:YES];
[self postNotificationOnMainThreadWithName:kGTMSessionFetcherStartedNotification
userInfo:nil
requireAsync:NO];
[self postNotificationOnMainThreadWithName:kGTMSessionFetcherStartedNotification userInfo:nil];
// The service needs to know our task if it is serving as NSURLSession delegate.
[_service fetcherDidBeginFetching:self];
@@ -956,6 +1047,30 @@ static GTMSessionFetcherTestBlock _Nullable gGlobalTestBlock;
// of the session task.
[newSessionTask resume];
}
BOOL stopped;
@synchronized(self) {
GTMSessionMonitorSynchronized(self);
stopped = _userStoppedFetching;
if (!stopped) {
_startingState = kStartingStateNone;
}
}
// If a `-stopFetching` came in between where the connection was finally started (see above), then
// `-stopFetching` itself didn't do anything because it would be a race, so we have to trigger
// the cleanup work here manually.
//
if (stopped) {
if (self.stopFetchingTriggersCompletionHandler) {
NSError *error = [NSError errorWithDomain:kGTMSessionFetcherErrorDomain
code:GTMSessionFetcherErrorUserCancelled
userInfo:nil];
// Fetch started, can't use `-failToBeginFetchWithError:`.
[self finishWithError:error shouldRetry:NO];
} else {
[self stopFetchReleasingCallbacks:YES];
}
}
}
// Helper method to create a new NSURLSession for this fetcher. Because the original
@@ -984,14 +1099,12 @@ static GTMSessionFetcherTestBlock _Nullable gGlobalTestBlock;
#if !GTM_ALLOW_INSECURE_REQUESTS
#if GTM_SDK_REQUIRES_TLSMINIMUMSUPPORTEDPROTOCOLVERSION
_configuration.TLSMinimumSupportedProtocolVersion = tls_protocol_version_TLSv12;
#elif GTM_SDK_SUPPORTS_TLSMINIMUMSUPPORTEDPROTOCOLVERSION
if (@available(iOS 13, tvOS 13, macOS 10.15, *)) {
#else
if (@available(iOS 13, *)) {
_configuration.TLSMinimumSupportedProtocolVersion = tls_protocol_version_TLSv12;
} else {
_configuration.TLSMinimumSupportedProtocol = kTLSProtocol12;
}
#else
_configuration.TLSMinimumSupportedProtocol = kTLSProtocol12;
#endif // GTM_SDK_REQUIRES_TLSMINIMUMSUPPORTEDPROTOCOLVERSION
#endif
} // !_configuration
@@ -1029,11 +1142,15 @@ static GTMSessionFetcherTestBlock _Nullable gGlobalTestBlock;
// sets it in |fetchRequest| and continues the request.
- (void)updateUserAgentAsynchronouslyForRequest:(NSMutableURLRequest *)fetchRequest
userAgentProvider:(id<GTMUserAgentProvider>)userAgentProvider
mayDelay:(BOOL)mayDelay
mayAuthorize:(BOOL)mayAuthorize
mayDecorate:(BOOL)mayDecorate {
GTMSESSION_LOG_DEBUG_VERBOSE(
@"GTMSessionFetcher fetching User-Agent from GTMUserAgentProvider %@...", _userAgentProvider);
if (![self startingState:kStartingStateCalculatingUA]) {
return;
}
__weak __typeof__(self) weakSelf = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
__strong __typeof__(self) strongSelf = weakSelf;
@@ -1048,23 +1165,17 @@ static GTMSessionFetcherTestBlock _Nullable gGlobalTestBlock;
@"GTMUserAgentProvider %@ should have cached user agent now that it's "
@"calculated, but returned nil",
userAgentProvider);
BOOL shouldStop;
@synchronized(strongSelf) {
GTMSessionMonitorSynchronized(strongSelf);
// If `stopFetching` was called, do nothing, since the fetch was in a delay state
// any needed callback already happened.
if (strongSelf->_userStoppedFetching) {
return;
}
[strongSelf->_request setValue:userAgent forHTTPHeaderField:@"User-Agent"];
shouldStop = strongSelf->_userStoppedFetching;
}
if (shouldStop) {
NSError *error = [NSError errorWithDomain:kGTMSessionFetcherErrorDomain
code:GTMSessionFetcherErrorUserCancelled
userInfo:nil];
[strongSelf invokeFetchCallbacksOnCallbackQueueWithData:nil
error:error
mayDecorate:NO
shouldReleaseCallbacks:YES];
} else {
[strongSelf beginFetchMayDelay:mayDelay mayAuthorize:mayAuthorize mayDecorate:mayDecorate];
strongSelf->_startingState = kStartingStateNone;
}
[strongSelf beginFetchMayDelay:NO mayAuthorize:mayAuthorize mayDecorate:mayDecorate];
});
}
@@ -1553,18 +1664,39 @@ NSData *_Nullable GTMDataFromInputStream(NSInputStream *inputStream, NSError **o
} // @synchronized(self)
}
- (nullable NSDictionary *)sessionUserInfo {
- (nullable NSDictionary<NSString *, NSString *> *)sessionUserInfo {
@synchronized(self) {
GTMSessionMonitorSynchronized(self);
if (_sessionUserInfo == nil) {
// We'll return the metadata dictionary with internal keys removed. This avoids the user
// re-using the userInfo dictionary later and accidentally including the internal keys.
// Just incase something got corrupted in storage and parsed back out differently, ensure
// the api contract on types is still valid.
NSMutableDictionary *metadata = [[self sessionIdentifierMetadataUnsynchronized] mutableCopy];
NSSet *keysToRemove = [metadata keysOfEntriesPassingTest:^BOOL(id key, id obj, BOOL *stop) {
return [key hasPrefix:@"_"];
return ![key isKindOfClass:[NSString class]] || ![obj isKindOfClass:[NSString class]] ||
[key hasPrefix:@"_"];
}];
#if DEBUG
// If we're pruning, give warnings about the things that were invalid as some bug has slipped
// through.
if (keysToRemove.count) {
[metadata enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj,
BOOL *_Nonnull stop) {
if (![key isKindOfClass:[NSString class]]) {
GTMSESSION_LOG_DEBUG(
@"InternalError: restoring sessionUserInfo is pruning a non String key: %@", key);
} else if (![obj isKindOfClass:[NSString class]]) {
GTMSESSION_LOG_DEBUG(
@"InternalError: restoring sessionUserInfo is pruning a non String value: %@: %@",
key, obj);
}
}];
}
#endif // DEBUG
[metadata removeObjectsForKeys:[keysToRemove allObjects]];
if (metadata.count > 0) {
_sessionUserInfo = metadata;
}
@@ -1573,7 +1705,26 @@ NSData *_Nullable GTMDataFromInputStream(NSInputStream *inputStream, NSError **o
} // @synchronized(self)
}
- (void)setSessionUserInfo:(nullable NSDictionary *)dictionary {
- (void)setSessionUserInfo:(nullable NSDictionary<NSString *, NSString *> *)dictionary {
[dictionary enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj,
BOOL *_Nonnull stop) {
if (![key isKindOfClass:[NSString class]]) {
[NSException raise:NSInvalidArgumentException
format:@"sessionUserInfo keys must be NSStrings: %@", key];
}
if ([key hasPrefix:@"_"]) {
[NSException
raise:NSInvalidArgumentException
format:
@"sessionUserInfo keys starting with an underscore are reserved for the library: %@",
key];
}
if (![obj isKindOfClass:[NSString class]]) {
[NSException raise:NSInvalidArgumentException
format:@"Values in sessionUserInfo must be NSStrings: %@: %@", key, obj];
}
}];
@synchronized(self) {
GTMSessionMonitorSynchronized(self);
@@ -1668,15 +1819,61 @@ NSData *_Nullable GTMDataFromInputStream(NSInputStream *inputStream, NSError **o
_sessionIdentifierUUID = [[NSUUID UUID] UUIDString];
_sessionIdentifier =
[NSString stringWithFormat:@"%@_%@", kGTMSessionIdentifierPrefix, _sessionIdentifierUUID];
// Start with user-supplied keys so they cannot accidentally override the fetcher's keys.
NSMutableDictionary *metadataDict =
[NSMutableDictionary dictionaryWithDictionary:(NSDictionary *_Nonnull)_sessionUserInfo];
// sessionUserInfo was declared as `strong` (not `copy`), so it could have been modifed after
// having been set. So remove anything that breaks the contract.
NSSet *keysToRemove = [metadataDict keysOfEntriesPassingTest:^BOOL(id key, id obj, BOOL *stop) {
return ![key isKindOfClass:[NSString class]] || ![obj isKindOfClass:[NSString class]] ||
[key hasPrefix:@"_"];
}];
#if DEBUG
// If we're pruning, give warnings about the things that were invalid as some bug has slipped
// through.
if (keysToRemove.count) {
[metadataDict enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj,
BOOL *_Nonnull stop) {
if (![key isKindOfClass:[NSString class]]) {
GTMSESSION_LOG_DEBUG(
@"Warning: sessionUserInfo has been modifed to include a non String key: %@", key);
} else if (![obj isKindOfClass:[NSString class]]) {
GTMSESSION_LOG_DEBUG(
@"Warning: sessionUserInfo has been modifed to include a non String value: %@: %@",
key, obj);
}
}];
}
#endif // DEBUG
[metadataDict removeObjectsForKeys:[keysToRemove allObjects]];
if (metadataToInclude) {
#if DEBUG
[metadataToInclude enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj,
BOOL *_Nonnull stop) {
GTMSESSION_ASSERT_DEBUG([key isKindOfClass:[NSString class]],
@"metadataToInclude keys must be NSStrings: %@", key);
GTMSESSION_ASSERT_DEBUG([key hasPrefix:@"_"],
@"metadataToInclude should only have prefixed keys: %@ - %@", key,
obj);
}];
#endif
[metadataDict addEntriesFromDictionary:(NSDictionary *)metadataToInclude];
}
NSDictionary *defaultMetadataDict = [self sessionIdentifierDefaultMetadata];
if (defaultMetadataDict) {
#if DEBUG
[defaultMetadataDict enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj,
BOOL *_Nonnull stop) {
GTMSESSION_ASSERT_DEBUG([key isKindOfClass:[NSString class]],
@"defaultMetadataDict keys must be NSStrings: %@", key);
GTMSESSION_ASSERT_DEBUG([key hasPrefix:@"_"],
@"defaultMetadataDict should only have prefixed keys: %@ - %@", key,
obj);
}];
#endif
[metadataDict addEntriesFromDictionary:defaultMetadataDict];
}
if (metadataDict.count > 0) {
@@ -1737,6 +1934,8 @@ NSData *_Nullable GTMDataFromInputStream(NSInputStream *inputStream, NSError **o
// we need to tell UIApplication we're done.
UIBackgroundTaskIdentifier bgTaskID;
@synchronized(self) {
GTMSessionMonitorSynchronized(self);
bgTaskID = self.backgroundTaskIdentifier;
if (bgTaskID != UIBackgroundTaskInvalid) {
self.backgroundTaskIdentifier = UIBackgroundTaskInvalid;
@@ -1754,6 +1953,10 @@ NSData *_Nullable GTMDataFromInputStream(NSInputStream *inputStream, NSError **o
- (void)authorizeRequest {
GTMSessionCheckNotSynchronized(self);
if (![self startingState:kStartingStateAuthorizing]) {
return;
}
id authorizer = self.authorizer;
// Prefer the block-based implementation. This *is* a change in behavior, but if authorizers
// previously provided this method they would presumably assume they can be used for the same
@@ -1774,9 +1977,12 @@ NSData *_Nullable GTMDataFromInputStream(NSInputStream *inputStream, NSError **o
[authorizer authorizeRequest:mutableRequest delegate:self didFinishSelector:callbackSel];
} else {
GTMSESSION_ASSERT_DEBUG(authorizer == nil, @"invalid authorizer for fetch");
// No authorizing possible, and authorizing happens only after any delay;
// just begin fetching
// Should really never get here the main flow shouldn't have called here if there
// wasn't an authorizer, but for safety sake, continue on through the starting process.
@synchronized(self) {
GTMSessionMonitorSynchronized(self);
_startingState = kStartingStateNone;
}
[self beginFetchMayDelay:NO mayAuthorize:NO mayDecorate:YES];
}
}
@@ -1789,13 +1995,28 @@ NSData *_Nullable GTMDataFromInputStream(NSInputStream *inputStream, NSError **o
finishedWithError:(nullable NSError *)error {
GTMSessionCheckNotSynchronized(self);
@synchronized(self) {
GTMSessionMonitorSynchronized(self);
// If `stopFetching` was called, do nothing, since the fetch was in a delay state
// any needed callback already happened.
if (_userStoppedFetching) {
return;
}
if (error == nil) {
_request = authorizedRequest;
// Clear the delay state only if things aren't about to fail. Don't want to have a race
// between clearing the state and when the error is posted because some other thread happened
// to get in a call to
// `-stopFetching`.
_startingState = kStartingStateNone;
}
} // @synchronized(self)
if (error != nil) {
// We can't fetch without authorization
[self failToBeginFetchWithError:(NSError *_Nonnull)error];
} else {
@synchronized(self) {
_request = authorizedRequest;
}
[self beginFetchMayDelay:NO mayAuthorize:NO mayDecorate:YES];
}
}
@@ -1806,40 +2027,79 @@ NSData *_Nullable GTMDataFromInputStream(NSInputStream *inputStream, NSError **o
if (index >= decorators.count) {
GTMSESSION_LOG_DEBUG_VERBOSE(
@"GTMSessionFetcher decorate requestWillStart %zu decorators complete", decorators.count);
@synchronized(self) {
GTMSessionMonitorSynchronized(self);
_startingState = kStartingStateNone;
}
[self beginFetchMayDelay:NO mayAuthorize:NO mayDecorate:NO];
return;
}
if (index == 0) {
if (![self startingState:kStartingStateApplyingDecorators]) {
return;
}
}
__weak __typeof__(self) weakSelf = self;
id<GTMFetcherDecoratorProtocol> decorator = decorators[index];
GTMSESSION_LOG_DEBUG_VERBOSE(
@"GTMSessionFetcher decorate requestWillStart %zu decorators, index %zu, "
@"retry count %zu, decorator %@",
decorators.count, index, self.retryCount, decorator);
[decorator fetcherWillStart:self
completionHandler:^(NSURLRequest *_Nullable newRequest, NSError *_Nullable error) {
GTMSESSION_LOG_DEBUG_VERBOSE(
@"GTMSessionFetcher decorator requestWillStart index %zu "
@"complete, newRequest %@, error %@",
index, newRequest, error);
__strong __typeof__(self) strongSelf = weakSelf;
if (!strongSelf) {
GTMSESSION_LOG_DEBUG(@"GTMSessionFetcher destroyed before requestWillStart "
@"decorators completed, ignoring.");
return;
}
if (error) {
[self failToBeginFetchWithError:(NSError *_Nonnull)error];
return;
}
if (newRequest) {
// Copying `NSURLRequest` should be cheap, but in case profiling shows this
// operation is prohibitively expensive, this API might need to be changed to allow
// clients to manipulate `self.request` directly.
[strongSelf updateMutableRequest:[newRequest mutableCopy]];
}
[strongSelf applyDecoratorsAtRequestWillStart:decorators startingAtIndex:index + 1];
}];
[decorator
fetcherWillStart:self
completionHandler:^(NSURLRequest *_Nullable newRequest, NSError *_Nullable error) {
GTMSESSION_LOG_DEBUG_VERBOSE(@"GTMSessionFetcher decorator requestWillStart index %zu "
@"complete, newRequest %@, error %@",
index, newRequest, error);
__strong __typeof__(self) strongSelf = weakSelf;
if (!strongSelf) {
GTMSESSION_LOG_DEBUG(@"GTMSessionFetcher destroyed before requestWillStart "
@"decorators completed, ignoring.");
return;
}
BOOL shouldStop;
@synchronized(strongSelf) {
GTMSessionMonitorSynchronized(strongSelf);
shouldStop = strongSelf->_userStoppedFetching;
if (shouldStop) {
if (strongSelf->_stopFetchingTriggersCompletionHandler) {
// Override any error with the cancel error and then trigger the callbacks below.
//
// The callback for a cancel is sent from here instead of from within `-stopFetching`
// like is done for all the other pending states because if `-stopFetching` directly
// triggered the callbacks, the decorators would also get triggered and in a
// multithreaded case it would be possible for this current decorartor to have
// didFinish invoked at the same time but on a different thread; so triggering the
// failure completion here will avoid that.
error = [NSError errorWithDomain:kGTMSessionFetcherErrorDomain
code:GTMSessionFetcherErrorUserCancelled
userInfo:nil];
} else {
// Suppress any error from being sent due to the `-stopFetching`.
error = nil;
}
}
}
if (error) {
[strongSelf failToBeginFetchWithError:(NSError *_Nonnull)error];
return;
}
if (shouldStop) {
return;
}
if (newRequest) {
// Copying `NSURLRequest` should be cheap, but in case profiling shows this
// operation is prohibitively expensive, this API might need to be changed to allow
// clients to manipulate `self.request` directly.
[strongSelf updateMutableRequest:[newRequest mutableCopy]];
}
[strongSelf applyDecoratorsAtRequestWillStart:decorators startingAtIndex:index + 1];
}];
}
- (void)applyDecoratorsAtRequestDidFinish:(NSArray<id<GTMFetcherDecoratorProtocol>> *)decorators
@@ -1848,6 +2108,11 @@ NSData *_Nullable GTMDataFromInputStream(NSInputStream *inputStream, NSError **o
startingAtIndex:(NSUInteger)index
shouldReleaseCallbacks:(BOOL)shouldReleaseCallbacks {
GTMSessionCheckNotSynchronized(self);
// NOTE: At this point, the fetch is done, so if `-stopFetching` comes in now, it does not stop
// these from getting called as the completion is currently being invoked with the results of
// the fetch.
if (index >= decorators.count) {
GTMSESSION_LOG_DEBUG_VERBOSE(
@"GTMSessionFetcher decorate requestDidFinish %zu decorators complete", decorators.count);
@@ -2011,9 +2276,7 @@ NSData *_Nullable GTMDataFromInputStream(NSInputStream *inputStream, NSError **o
self.retryBlock = nil;
self.testBlock = nil;
self.resumeDataBlock = nil;
if (@available(iOS 10.0, *)) {
self.metricsCollectionBlock = nil;
}
self.metricsCollectionBlock = nil;
}
- (void)forgetSessionIdentifierForFetcher {
@@ -2033,14 +2296,38 @@ NSData *_Nullable GTMDataFromInputStream(NSInputStream *inputStream, NSError **o
// External stop method
- (void)stopFetching {
BOOL triggerCallback;
BOOL inBeginFetch;
BOOL stopTriggersHandler;
@synchronized(self) {
GTMSessionMonitorSynchronized(self);
stopTriggersHandler = self.stopFetchingTriggersCompletionHandler;
// Prevent enqueued callbacks from executing. The completion handler will still execute if
// the property `stopFetchingTriggersCompletionHandler` is `YES`.
_userStoppedFetching = YES;
// Some of the delayed states want the complition to be triggered from here so they don't have
// to do it during their flows for async work.
triggerCallback = (_startingState == kStartingStateServiceDelayed ||
_startingState == kStartingStateAuthorizing ||
_startingState == kStartingStateCalculatingUA) &&
stopTriggersHandler;
// If literally in `-beginFetchMayDelay:mayAuthorize:mayDecorate:`, it will handle the work to
// stop.
inBeginFetch = _startingState == kStartingStateStartingUp;
} // @synchronized(self)
[self stopFetchReleasingCallbacks:!self.stopFetchingTriggersCompletionHandler];
if (triggerCallback) {
NSError *error = [NSError errorWithDomain:kGTMSessionFetcherErrorDomain
code:GTMSessionFetcherErrorUserCancelled
userInfo:nil];
[self failToBeginFetchWithError:error];
} else if (!inBeginFetch) {
[self stopFetchReleasingCallbacks:!stopTriggersHandler];
}
}
// Cancel the fetch of the URL that's currently in progress.
@@ -2052,7 +2339,7 @@ NSData *_Nullable GTMDataFromInputStream(NSInputStream *inputStream, NSError **o
[self removePersistedBackgroundSessionFromDefaults];
GTMSessionFetcherService *service;
NSMutableURLRequest *request;
NSMutableURLRequest *requestForStopAuth;
// If the task or the retry timer is all that's retaining the fetcher,
// we want to be sure this instance survives stopping at least long enough for
@@ -2071,7 +2358,8 @@ NSData *_Nullable GTMDataFromInputStream(NSInputStream *inputStream, NSError **o
_hasStoppedFetching = YES;
service = _service;
request = _request;
// Only will need to stop authorization if in an authorizing state.
requestForStopAuth = (_startingState == kStartingStateAuthorizing) ? _request : nil;
if (_sessionTask) {
// In case cancelling the task or session calls this recursively, we want
@@ -2146,7 +2434,9 @@ NSData *_Nullable GTMDataFromInputStream(NSInputStream *inputStream, NSError **o
[self sendStopNotificationIfNeeded];
}
[_authorizer stopAuthorizationForRequest:request];
if (requestForStopAuth) {
[_authorizer stopAuthorizationForRequest:requestForStopAuth];
}
if (shouldReleaseCallbacks) {
[self releaseCallbacks];
@@ -2181,9 +2471,7 @@ NSData *_Nullable GTMDataFromInputStream(NSInputStream *inputStream, NSError **o
} // @synchronized(self)
if (sendNow) {
[self postNotificationOnMainThreadWithName:kGTMSessionFetcherStoppedNotification
userInfo:nil
requireAsync:NO];
[self postNotificationOnMainThreadWithName:kGTMSessionFetcherStoppedNotification userInfo:nil];
}
}
@@ -2550,8 +2838,7 @@ static _Nullable id<GTMUIApplicationProtocol> gSubstituteUIApp;
NSString *redirectScheme = redirectRequestURL.scheme;
// If no change in scheme with redirect, just return the redirect.
if (originalScheme != nil &&
redirectScheme != nil &&
if (originalScheme != nil && redirectScheme != nil &&
[originalScheme caseInsensitiveCompare:redirectScheme] == NSOrderedSame) {
return redirectRequestURL;
}
@@ -2765,31 +3052,56 @@ static _Nullable id<GTMUIApplicationProtocol> gSubstituteUIApp;
}
[self postNotificationOnMainThreadWithName:
kGTMSessionFetcherCompletionInvokedNotification
userInfo:userInfo
requireAsync:NO];
userInfo:userInfo];
}];
}
}
- (void)postNotificationOnMainThreadWithName:(NSString *)noteName
userInfo:(nullable NSDictionary *)userInfo
requireAsync:(BOOL)requireAsync {
dispatch_block_t postBlock = ^{
userInfo:(nullable NSDictionary *)userInfo {
// Historically, the notification has been posted immediately when on the main thread already, so
// that is continued to not break any existing code. However, if a notification gets deferred to
// be posted on the main thread, then even if some other code is one the main thread, it can't
// post it's notification before the deferred one is posted, otherwise you could get a stop and/or
// callback notification *before* the start notification. So if anything gets deferred to the main
// thread, an immediated post on the main thread also has to get delayed to maintain the order.
//
// This could get revisited to defer all notifications, but that should probably go out in a
// major version bump as it could be breaking to some usages who were dependent on the ordering.
GTMSessionCheckNotSynchronized(self);
BOOL canPostNow = NO;
@synchronized(self) {
GTMSessionMonitorSynchronized(self);
if (_pendingNotifications == 0 && [NSThread isMainThread]) {
canPostNow = YES;
} else {
// This one will get deferred, so increase the counter for deferred notifications.
_pendingNotifications++;
}
}
if (canPostNow) {
[[NSNotificationCenter defaultCenter] postNotificationName:noteName
object:self
userInfo:userInfo];
};
if ([NSThread isMainThread] && !requireAsync) {
// Post synchronously for compatibility with older code using the fetcher.
// Avoid calling out to other code from inside a sync block to avoid risk
// of a deadlock or of recursive sync.
GTMSessionCheckNotSynchronized(self);
postBlock();
} else {
dispatch_async(dispatch_get_main_queue(), postBlock);
dispatch_async(dispatch_get_main_queue(), ^{
@synchronized(self) {
GTMSessionMonitorSynchronized(self);
GTMSESSION_ASSERT_DEBUG(
self->_pendingNotifications > 0,
@"Internal error: firing pending notification when wasn't tracked: %lu",
(unsigned long)self->_pendingNotifications);
self->_pendingNotifications--;
}
[[NSNotificationCenter defaultCenter] postNotificationName:noteName
object:self
userInfo:userInfo];
});
}
}
@@ -3132,8 +3444,7 @@ static _Nullable id<GTMUIApplicationProtocol> gSubstituteUIApp;
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics
API_AVAILABLE(ios(10.0), macosx(10.12), tvos(10.0), watchos(6.0)) {
didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics {
@synchronized(self) {
GTMSessionMonitorSynchronized(self);
GTMSessionFetcherMetricsCollectionBlock metricsCollectionBlock = _metricsCollectionBlock;
@@ -3518,8 +3829,7 @@ static _Nullable id<GTMUIApplicationProtocol> gSubstituteUIApp;
} // @synchronized(self)
[self postNotificationOnMainThreadWithName:kGTMSessionFetcherRetryDelayStartedNotification
userInfo:nil
requireAsync:NO];
userInfo:nil];
}
- (void)retryTimerFired:(NSTimer *)timer {
@@ -3552,8 +3862,7 @@ static _Nullable id<GTMUIApplicationProtocol> gSubstituteUIApp;
if (shouldNotify) {
[self postNotificationOnMainThreadWithName:kGTMSessionFetcherRetryDelayStoppedNotification
userInfo:nil
requireAsync:NO];
userInfo:nil];
}
}