修改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

@@ -106,8 +106,6 @@ static NSString *const kGULDidRegisterForRemoteNotificationsSEL =
@"application:didRegisterForRemoteNotificationsWithDeviceToken:";
static NSString *const kGULDidFailToRegisterForRemoteNotificationsSEL =
@"application:didFailToRegisterForRemoteNotificationsWithError:";
static NSString *const kGULDidReceiveRemoteNotificationSEL =
@"application:didReceiveRemoteNotification:";
static NSString *const kGULDidReceiveRemoteNotificationWithCompletionSEL =
@"application:didReceiveRemoteNotification:fetchCompletionHandler:";
@@ -218,27 +216,27 @@ static dispatch_once_t sProxyAppDelegateRemoteNotificationOnceToken;
@"AppDelegateProxy interceptor does not conform to UIApplicationDelegate");
if (!interceptor) {
GULLogError(kGULLoggerSwizzler, NO,
[NSString stringWithFormat:@"I-SWZ%06ld",
(long)kGULSwizzlerMessageCodeAppDelegateSwizzling000],
@"AppDelegateProxy cannot add nil interceptor.");
GULOSLogError(kGULLogSubsystem, kGULLoggerSwizzler, NO,
[NSString stringWithFormat:@"I-SWZ%06ld",
(long)kGULSwizzlerMessageCodeAppDelegateSwizzling000],
@"AppDelegateProxy cannot add nil interceptor.");
return nil;
}
if (![interceptor conformsToProtocol:@protocol(GULApplicationDelegate)]) {
GULLogError(kGULLoggerSwizzler, NO,
[NSString stringWithFormat:@"I-SWZ%06ld",
(long)kGULSwizzlerMessageCodeAppDelegateSwizzling001],
@"AppDelegateProxy interceptor does not conform to UIApplicationDelegate");
GULOSLogError(kGULLogSubsystem, kGULLoggerSwizzler, NO,
[NSString stringWithFormat:@"I-SWZ%06ld",
(long)kGULSwizzlerMessageCodeAppDelegateSwizzling001],
@"AppDelegateProxy interceptor does not conform to UIApplicationDelegate");
return nil;
}
// The ID should be the same given the same interceptor object.
NSString *interceptorID = [NSString stringWithFormat:@"%@%p", kGULAppDelegatePrefix, interceptor];
if (!interceptorID.length) {
GULLogError(kGULLoggerSwizzler, NO,
[NSString stringWithFormat:@"I-SWZ%06ld",
(long)kGULSwizzlerMessageCodeAppDelegateSwizzling002],
@"AppDelegateProxy cannot create Interceptor ID.");
GULOSLogError(kGULLogSubsystem, kGULLoggerSwizzler, NO,
[NSString stringWithFormat:@"I-SWZ%06ld",
(long)kGULSwizzlerMessageCodeAppDelegateSwizzling002],
@"AppDelegateProxy cannot create Interceptor ID.");
return nil;
}
GULZeroingWeakContainer *weakObject = [[GULZeroingWeakContainer alloc] init];
@@ -253,21 +251,21 @@ static dispatch_once_t sProxyAppDelegateRemoteNotificationOnceToken;
@"AppDelegateProxy cannot unregister empty interceptor ID.");
if (!interceptorID) {
GULLogError(kGULLoggerSwizzler, NO,
[NSString stringWithFormat:@"I-SWZ%06ld",
(long)kGULSwizzlerMessageCodeAppDelegateSwizzling003],
@"AppDelegateProxy cannot unregister empty interceptor ID.");
GULOSLogError(kGULLogSubsystem, kGULLoggerSwizzler, NO,
[NSString stringWithFormat:@"I-SWZ%06ld",
(long)kGULSwizzlerMessageCodeAppDelegateSwizzling003],
@"AppDelegateProxy cannot unregister empty interceptor ID.");
return;
}
GULZeroingWeakContainer *weakContainer = [GULAppDelegateSwizzler interceptors][interceptorID];
if (!weakContainer.object) {
GULLogError(kGULLoggerSwizzler, NO,
[NSString stringWithFormat:@"I-SWZ%06ld",
(long)kGULSwizzlerMessageCodeAppDelegateSwizzling004],
@"AppDelegateProxy cannot unregister interceptor that was not registered. "
"Interceptor ID %@",
interceptorID);
GULOSLogError(kGULLogSubsystem, kGULLoggerSwizzler, NO,
[NSString stringWithFormat:@"I-SWZ%06ld",
(long)kGULSwizzlerMessageCodeAppDelegateSwizzling004],
@"AppDelegateProxy cannot unregister interceptor that was not registered. "
"Interceptor ID %@",
interceptorID);
return;
}
@@ -345,12 +343,13 @@ static dispatch_once_t sProxyAppDelegateRemoteNotificationOnceToken;
[NSString stringWithFormat:@"%@-%@", classNameWithPrefix, [NSUUID UUID].UUIDString];
if (NSClassFromString(newClassName)) {
GULLogError(kGULLoggerSwizzler, NO,
[NSString stringWithFormat:@"I-SWZ%06ld",
(long)kGULSwizzlerMessageCodeAppDelegateSwizzling005],
@"Cannot create a proxy for App Delegate. Subclass already exists. Original Class: "
@"%@, subclass: %@",
NSStringFromClass(realClass), newClassName);
GULOSLogError(
kGULLogSubsystem, kGULLoggerSwizzler, NO,
[NSString
stringWithFormat:@"I-SWZ%06ld", (long)kGULSwizzlerMessageCodeAppDelegateSwizzling005],
@"Cannot create a proxy for App Delegate. Subclass already exists. Original Class: "
@"%@, subclass: %@",
NSStringFromClass(realClass), newClassName);
return nil;
}
@@ -358,12 +357,13 @@ static dispatch_once_t sProxyAppDelegateRemoteNotificationOnceToken;
// size.
Class appDelegateSubClass = objc_allocateClassPair(realClass, newClassName.UTF8String, 0);
if (appDelegateSubClass == Nil) {
GULLogError(kGULLoggerSwizzler, NO,
[NSString stringWithFormat:@"I-SWZ%06ld",
(long)kGULSwizzlerMessageCodeAppDelegateSwizzling006],
@"Cannot create a proxy for App Delegate. Subclass already exists. Original Class: "
@"%@, subclass: Nil",
NSStringFromClass(realClass));
GULOSLogError(
kGULLogSubsystem, kGULLoggerSwizzler, NO,
[NSString
stringWithFormat:@"I-SWZ%06ld", (long)kGULSwizzlerMessageCodeAppDelegateSwizzling006],
@"Cannot create a proxy for App Delegate. Subclass already exists. Original Class: "
@"%@, subclass: Nil",
NSStringFromClass(realClass));
return nil;
}
@@ -438,12 +438,13 @@ static dispatch_once_t sProxyAppDelegateRemoteNotificationOnceToken;
// cannot have more ivars/properties than its superclass since it will cause an offset in memory
// that can lead to overwriting the isa of an object in the next frame.
if (class_getInstanceSize(realClass) != class_getInstanceSize(appDelegateSubClass)) {
GULLogError(kGULLoggerSwizzler, NO,
[NSString stringWithFormat:@"I-SWZ%06ld",
(long)kGULSwizzlerMessageCodeAppDelegateSwizzling007],
@"Cannot create subclass of App Delegate, because the created subclass is not the "
@"same size. %@",
NSStringFromClass(realClass));
GULOSLogError(
kGULLogSubsystem, kGULLoggerSwizzler, NO,
[NSString
stringWithFormat:@"I-SWZ%06ld", (long)kGULSwizzlerMessageCodeAppDelegateSwizzling007],
@"Cannot create subclass of App Delegate, because the created subclass is not the "
@"same size. %@",
NSStringFromClass(realClass));
NSAssert(NO, @"Classes must be the same size to swizzle isa");
return nil;
}
@@ -451,12 +452,12 @@ static dispatch_once_t sProxyAppDelegateRemoteNotificationOnceToken;
// Make the newly created class to be the subclass of the real App Delegate class.
objc_registerClassPair(appDelegateSubClass);
if (object_setClass(appDelegate, appDelegateSubClass)) {
GULLogDebug(kGULLoggerSwizzler, NO,
[NSString stringWithFormat:@"I-SWZ%06ld",
(long)kGULSwizzlerMessageCodeAppDelegateSwizzling008],
@"Successfully created App Delegate Proxy automatically. To disable the "
@"proxy, set the flag %@ to NO (Boolean) in the Info.plist",
[GULAppDelegateSwizzler correctAppDelegateProxyKey]);
GULOSLogDebug(kGULLogSubsystem, kGULLoggerSwizzler, NO,
[NSString stringWithFormat:@"I-SWZ%06ld",
(long)kGULSwizzlerMessageCodeAppDelegateSwizzling008],
@"Successfully created App Delegate Proxy automatically. To disable the "
@"proxy, set the flag %@ to NO (Boolean) in the Info.plist",
[GULAppDelegateSwizzler correctAppDelegateProxyKey]);
}
return appDelegateSubClass;
@@ -499,38 +500,18 @@ static dispatch_once_t sProxyAppDelegateRemoteNotificationOnceToken;
realClass:realClass
storeDestinationImplementationTo:realImplementationsBySelector];
// For application:didReceiveRemoteNotification:
SEL didReceiveRemoteNotificationSEL = NSSelectorFromString(kGULDidReceiveRemoteNotificationSEL);
SEL didReceiveRemoteNotificationDonorSEL = @selector(application:
donor_didReceiveRemoteNotification:);
[self proxyDestinationSelector:didReceiveRemoteNotificationSEL
implementationsFromSourceSelector:didReceiveRemoteNotificationDonorSEL
fromClass:[GULAppDelegateSwizzler class]
toClass:appDelegateSubClass
realClass:realClass
storeDestinationImplementationTo:realImplementationsBySelector];
// For application:didReceiveRemoteNotification:fetchCompletionHandler:
#if !TARGET_OS_WATCH && !TARGET_OS_OSX
SEL didReceiveRemoteNotificationWithCompletionSEL =
NSSelectorFromString(kGULDidReceiveRemoteNotificationWithCompletionSEL);
SEL didReceiveRemoteNotificationWithCompletionDonorSEL =
@selector(application:donor_didReceiveRemoteNotification:fetchCompletionHandler:);
if ([appDelegate respondsToSelector:didReceiveRemoteNotificationWithCompletionSEL]) {
// Only add the application:didReceiveRemoteNotification:fetchCompletionHandler: method if
// the original AppDelegate implements it.
// This fixes a bug if an app only implements application:didReceiveRemoteNotification:
// (if we add the method with completion, iOS sees that one exists and does not call
// the method without the completion, which in this case is the only one the app implements).
[self proxyDestinationSelector:didReceiveRemoteNotificationWithCompletionSEL
implementationsFromSourceSelector:didReceiveRemoteNotificationWithCompletionDonorSEL
fromClass:[GULAppDelegateSwizzler class]
toClass:appDelegateSubClass
realClass:realClass
storeDestinationImplementationTo:realImplementationsBySelector];
}
[self proxyDestinationSelector:didReceiveRemoteNotificationWithCompletionSEL
implementationsFromSourceSelector:didReceiveRemoteNotificationWithCompletionDonorSEL
fromClass:[GULAppDelegateSwizzler class]
toClass:appDelegateSubClass
realClass:realClass
storeDestinationImplementationTo:realImplementationsBySelector];
#endif // !TARGET_OS_WATCH && !TARGET_OS_OSX
}
@@ -626,11 +607,12 @@ static dispatch_once_t sProxyAppDelegateRemoteNotificationOnceToken;
IMP methodIMP = method_getImplementation(method);
const char *types = method_getTypeEncoding(method);
if (!class_addMethod(toClass, destinationSelector, methodIMP, types)) {
GULLogWarning(kGULLoggerSwizzler, NO,
[NSString stringWithFormat:@"I-SWZ%06ld",
(long)kGULSwizzlerMessageCodeAppDelegateSwizzling009],
@"Cannot copy method to destination selector %@ as it already exists",
NSStringFromSelector(destinationSelector));
GULOSLogWarning(
kGULLogSubsystem, kGULLoggerSwizzler, NO,
[NSString
stringWithFormat:@"I-SWZ%06ld", (long)kGULSwizzlerMessageCodeAppDelegateSwizzling009],
@"Cannot copy method to destination selector %@ as it already exists",
NSStringFromSelector(destinationSelector));
}
}
@@ -662,8 +644,8 @@ static dispatch_once_t sProxyAppDelegateRemoteNotificationOnceToken;
GULZeroingWeakContainer *interceptorContainer = obj;
id interceptor = interceptorContainer.object;
if (!interceptor) {
GULLogWarning(
kGULLoggerSwizzler, NO,
GULOSLogWarning(
kGULLogSubsystem, kGULLoggerSwizzler, NO,
[NSString
stringWithFormat:@"I-SWZ%06ld", (long)kGULSwizzlerMessageCodeAppDelegateSwizzling010],
@"AppDelegateProxy cannot find interceptor with ID %@. Removing the interceptor.", key);
@@ -720,9 +702,7 @@ static dispatch_once_t sProxyAppDelegateRemoteNotificationOnceToken;
#endif // TARGET_OS_IOS || TARGET_OS_TV
// TODO(Xcode 15): When Xcode 15 is the minimum supported Xcode version,
// it will be unnecessary to check if `TARGET_OS_VISION` is defined.
#if TARGET_OS_IOS && (!defined(TARGET_OS_VISION) || !TARGET_OS_VISION)
#if TARGET_OS_IOS
- (BOOL)application:(GULApplication *)application
openURL:(NSURL *)url
@@ -755,7 +735,7 @@ static dispatch_once_t sProxyAppDelegateRemoteNotificationOnceToken;
return returnedValue;
}
#endif // TARGET_OS_IOS && (!defined(TARGET_OS_VISION) || !TARGET_OS_VISION)
#endif // TARGET_OS_IOS
#pragma mark - [Donor Methods] Network overridden handler methods
@@ -765,7 +745,7 @@ static dispatch_once_t sProxyAppDelegateRemoteNotificationOnceToken;
#pragma clang diagnostic ignored "-Wstrict-prototypes"
- (void)application:(GULApplication *)application
handleEventsForBackgroundURLSession:(NSString *)identifier
completionHandler:(void (^)())completionHandler API_AVAILABLE(ios(7.0)) {
completionHandler:(void (^)())completionHandler {
#pragma clang diagnostic pop
SEL methodSelector = @selector(application:
handleEventsForBackgroundURLSession:completionHandler:);
@@ -954,35 +934,6 @@ static dispatch_once_t sProxyAppDelegateRemoteNotificationOnceToken;
}
#endif // !TARGET_OS_WATCH && !TARGET_OS_OSX
- (void)application:(GULApplication *)application
donor_didReceiveRemoteNotification:(NSDictionary *)userInfo {
SEL methodSelector = NSSelectorFromString(kGULDidReceiveRemoteNotificationSEL);
NSValue *didReceiveRemoteNotificationIMPPointer =
[GULAppDelegateSwizzler originalImplementationForSelector:methodSelector object:self];
GULRealDidReceiveRemoteNotificationIMP didReceiveRemoteNotificationIMP =
[didReceiveRemoteNotificationIMPPointer pointerValue];
// Notify interceptors.
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
[GULAppDelegateSwizzler
notifyInterceptorsWithMethodSelector:methodSelector
callback:^(id<GULApplicationDelegate> interceptor) {
NSInvocation *invocation = [GULAppDelegateSwizzler
appDelegateInvocationForSelector:methodSelector];
[invocation setTarget:interceptor];
[invocation setSelector:methodSelector];
[invocation setArgument:(void *)(&application) atIndex:2];
[invocation setArgument:(void *)(&userInfo) atIndex:3];
[invocation invoke];
}];
#pragma clang diagnostic pop
// Call the real implementation if the real App Delegate has any.
if (didReceiveRemoteNotificationIMP) {
didReceiveRemoteNotificationIMP(self, methodSelector, application, userInfo);
}
}
+ (nullable NSInvocation *)appDelegateInvocationForSelector:(SEL)selector {
struct objc_method_description methodDescription =
protocol_getMethodDescription(@protocol(GULApplicationDelegate), selector, NO, YES);
@@ -996,8 +947,8 @@ static dispatch_once_t sProxyAppDelegateRemoteNotificationOnceToken;
+ (void)proxyAppDelegate:(id<GULApplicationDelegate>)appDelegate {
if (![appDelegate conformsToProtocol:@protocol(GULApplicationDelegate)]) {
GULLogNotice(
kGULLoggerSwizzler, NO,
GULOSLogNotice(
kGULLogSubsystem, kGULLoggerSwizzler, NO,
[NSString
stringWithFormat:@"I-SWZ%06ld",
(long)kGULSwizzlerMessageCodeAppDelegateSwizzlingInvalidAppDelegate],
@@ -1009,20 +960,20 @@ static dispatch_once_t sProxyAppDelegateRemoteNotificationOnceToken;
id<GULApplicationDelegate> originalDelegate = appDelegate;
// Do not create a subclass if it is not enabled.
if (![GULAppDelegateSwizzler isAppDelegateProxyEnabled]) {
GULLogNotice(kGULLoggerSwizzler, NO,
[NSString stringWithFormat:@"I-SWZ%06ld",
(long)kGULSwizzlerMessageCodeAppDelegateSwizzling011],
@"App Delegate Proxy is disabled. %@",
[GULAppDelegateSwizzler correctAlternativeWhenAppDelegateProxyNotCreated]);
GULOSLogNotice(kGULLogSubsystem, kGULLoggerSwizzler, NO,
[NSString stringWithFormat:@"I-SWZ%06ld",
(long)kGULSwizzlerMessageCodeAppDelegateSwizzling011],
@"App Delegate Proxy is disabled. %@",
[GULAppDelegateSwizzler correctAlternativeWhenAppDelegateProxyNotCreated]);
return;
}
// Do not accept nil delegate.
if (!originalDelegate) {
GULLogError(kGULLoggerSwizzler, NO,
[NSString stringWithFormat:@"I-SWZ%06ld",
(long)kGULSwizzlerMessageCodeAppDelegateSwizzling012],
@"Cannot create App Delegate Proxy because App Delegate instance is nil. %@",
[GULAppDelegateSwizzler correctAlternativeWhenAppDelegateProxyNotCreated]);
GULOSLogError(kGULLogSubsystem, kGULLoggerSwizzler, NO,
[NSString stringWithFormat:@"I-SWZ%06ld",
(long)kGULSwizzlerMessageCodeAppDelegateSwizzling012],
@"Cannot create App Delegate Proxy because App Delegate instance is nil. %@",
[GULAppDelegateSwizzler correctAlternativeWhenAppDelegateProxyNotCreated]);
return;
}
@@ -1031,11 +982,11 @@ static dispatch_once_t sProxyAppDelegateRemoteNotificationOnceToken;
gAppDelegateSubclass = [self createSubclassWithObject:originalDelegate];
[self reassignAppDelegate];
} @catch (NSException *exception) {
GULLogError(kGULLoggerSwizzler, NO,
[NSString stringWithFormat:@"I-SWZ%06ld",
(long)kGULSwizzlerMessageCodeAppDelegateSwizzling013],
@"Cannot create App Delegate Proxy. %@",
[GULAppDelegateSwizzler correctAlternativeWhenAppDelegateProxyNotCreated]);
GULOSLogError(kGULLogSubsystem, kGULLoggerSwizzler, NO,
[NSString stringWithFormat:@"I-SWZ%06ld",
(long)kGULSwizzlerMessageCodeAppDelegateSwizzling013],
@"Cannot create App Delegate Proxy. %@",
[GULAppDelegateSwizzler correctAlternativeWhenAppDelegateProxyNotCreated]);
return;
}
}

View File

@@ -108,17 +108,19 @@ static NSString *const kGULSceneDelegatePrefix = @"GUL_";
@"SceneDelegateProxy interceptor does not conform to UIApplicationDelegate");
if (!interceptor) {
GULLogError(kGULLoggerSwizzler, NO,
[NSString stringWithFormat:@"I-SWZ%06ld",
(long)kGULSwizzlerMessageCodeSceneDelegateSwizzling000],
@"SceneDelegateProxy cannot add nil interceptor.");
GULOSLogError(
kGULLogSubsystem, kGULLoggerSwizzler, NO,
[NSString
stringWithFormat:@"I-SWZ%06ld", (long)kGULSwizzlerMessageCodeSceneDelegateSwizzling000],
@"SceneDelegateProxy cannot add nil interceptor.");
return nil;
}
if (![interceptor conformsToProtocol:@protocol(UISceneDelegate)]) {
GULLogError(kGULLoggerSwizzler, NO,
[NSString stringWithFormat:@"I-SWZ%06ld",
(long)kGULSwizzlerMessageCodeSceneDelegateSwizzling001],
@"SceneDelegateProxy interceptor does not conform to UIApplicationDelegate");
GULOSLogError(
kGULLogSubsystem, kGULLoggerSwizzler, NO,
[NSString
stringWithFormat:@"I-SWZ%06ld", (long)kGULSwizzlerMessageCodeSceneDelegateSwizzling001],
@"SceneDelegateProxy interceptor does not conform to UIApplicationDelegate");
return nil;
}
@@ -126,10 +128,11 @@ static NSString *const kGULSceneDelegatePrefix = @"GUL_";
NSString *interceptorID =
[NSString stringWithFormat:@"%@%p", kGULSceneDelegatePrefix, interceptor];
if (!interceptorID.length) {
GULLogError(kGULLoggerSwizzler, NO,
[NSString stringWithFormat:@"I-SWZ%06ld",
(long)kGULSwizzlerMessageCodeSceneDelegateSwizzling002],
@"SceneDelegateProxy cannot create Interceptor ID.");
GULOSLogError(
kGULLogSubsystem, kGULLoggerSwizzler, NO,
[NSString
stringWithFormat:@"I-SWZ%06ld", (long)kGULSwizzlerMessageCodeSceneDelegateSwizzling002],
@"SceneDelegateProxy cannot create Interceptor ID.");
return nil;
}
GULSceneZeroingWeakContainer *weakObject = [[GULSceneZeroingWeakContainer alloc] init];
@@ -144,22 +147,24 @@ static NSString *const kGULSceneDelegatePrefix = @"GUL_";
@"SceneDelegateProxy cannot unregister empty interceptor ID.");
if (!interceptorID) {
GULLogError(kGULLoggerSwizzler, NO,
[NSString stringWithFormat:@"I-SWZ%06ld",
(long)kGULSwizzlerMessageCodeSceneDelegateSwizzling003],
@"SceneDelegateProxy cannot unregister empty interceptor ID.");
GULOSLogError(
kGULLogSubsystem, kGULLoggerSwizzler, NO,
[NSString
stringWithFormat:@"I-SWZ%06ld", (long)kGULSwizzlerMessageCodeSceneDelegateSwizzling003],
@"SceneDelegateProxy cannot unregister empty interceptor ID.");
return;
}
GULSceneZeroingWeakContainer *weakContainer =
[GULSceneDelegateSwizzler interceptors][interceptorID];
if (!weakContainer.object) {
GULLogError(kGULLoggerSwizzler, NO,
[NSString stringWithFormat:@"I-SWZ%06ld",
(long)kGULSwizzlerMessageCodeSceneDelegateSwizzling004],
@"SceneDelegateProxy cannot unregister interceptor that was not registered. "
"Interceptor ID %@",
interceptorID);
GULOSLogError(
kGULLogSubsystem, kGULLoggerSwizzler, NO,
[NSString
stringWithFormat:@"I-SWZ%06ld", (long)kGULSwizzlerMessageCodeSceneDelegateSwizzling004],
@"SceneDelegateProxy cannot unregister interceptor that was not registered. "
"Interceptor ID %@",
interceptorID);
return;
}
@@ -247,8 +252,8 @@ static NSString *const kGULSceneDelegatePrefix = @"GUL_";
IMP methodIMP = method_getImplementation(method);
const char *types = method_getTypeEncoding(method);
if (!class_addMethod(toClass, destinationSelector, methodIMP, types)) {
GULLogWarning(
kGULLoggerSwizzler, NO,
GULOSLogWarning(
kGULLogSubsystem, kGULLoggerSwizzler, NO,
[NSString
stringWithFormat:@"I-SWZ%06ld", (long)kGULSwizzlerMessageCodeSceneDelegateSwizzling009],
@"Cannot copy method to destination selector %@ as it already exists",
@@ -285,8 +290,8 @@ static NSString *const kGULSceneDelegatePrefix = @"GUL_";
GULSceneZeroingWeakContainer *interceptorContainer = obj;
id interceptor = interceptorContainer.object;
if (!interceptor) {
GULLogWarning(
kGULLoggerSwizzler, NO,
GULOSLogWarning(
kGULLogSubsystem, kGULLoggerSwizzler, NO,
[NSString stringWithFormat:@"I-SWZ%06ld",
(long)kGULSwizzlerMessageCodeSceneDelegateSwizzling010],
@"SceneDelegateProxy cannot find interceptor with ID %@. Removing the interceptor.", key);
@@ -351,8 +356,8 @@ static NSString *const kGULSceneDelegatePrefix = @"GUL_";
[NSString stringWithFormat:@"%@-%@", classNameWithPrefix, [NSUUID UUID].UUIDString];
if (NSClassFromString(newClassName)) {
GULLogError(
kGULLoggerSwizzler, NO,
GULOSLogError(
kGULLogSubsystem, kGULLoggerSwizzler, NO,
[NSString
stringWithFormat:@"I-SWZ%06ld",
(long)
@@ -367,8 +372,8 @@ static NSString *const kGULSceneDelegatePrefix = @"GUL_";
// size.
Class sceneDelegateSubClass = objc_allocateClassPair(realClass, newClassName.UTF8String, 0);
if (sceneDelegateSubClass == Nil) {
GULLogError(
kGULLoggerSwizzler, NO,
GULOSLogError(
kGULLogSubsystem, kGULLoggerSwizzler, NO,
[NSString
stringWithFormat:@"I-SWZ%06ld",
(long)
@@ -401,8 +406,8 @@ static NSString *const kGULSceneDelegatePrefix = @"GUL_";
// cannot have more ivars/properties than its superclass since it will cause an offset in memory
// that can lead to overwriting the isa of an object in the next frame.
if (class_getInstanceSize(realClass) != class_getInstanceSize(sceneDelegateSubClass)) {
GULLogError(
kGULLoggerSwizzler, NO,
GULOSLogError(
kGULLogSubsystem, kGULLoggerSwizzler, NO,
[NSString
stringWithFormat:@"I-SWZ%06ld",
(long)
@@ -417,8 +422,8 @@ static NSString *const kGULSceneDelegatePrefix = @"GUL_";
// Make the newly created class to be the subclass of the real Scene Delegate class.
objc_registerClassPair(sceneDelegateSubClass);
if (object_setClass(scene.delegate, sceneDelegateSubClass)) {
GULLogDebug(
kGULLoggerSwizzler, NO,
GULOSLogDebug(
kGULLogSubsystem, kGULLoggerSwizzler, NO,
[NSString
stringWithFormat:@"I-SWZ%06ld",
(long)

View File

@@ -16,7 +16,7 @@
#import <Foundation/Foundation.h>
#if TARGET_OS_IOS || TARGET_OS_TV || (defined(TARGET_OS_VISION) && TARGET_OS_VISION)
#if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_VISION
#import <UIKit/UIKit.h>

View File

@@ -17,11 +17,11 @@
#import <Foundation/Foundation.h>
#import <TargetConditionals.h>
#if !TARGET_OS_OSX
#if __has_include(<UIKit/UIKit.h>)
#import <UIKit/UIKit.h>
#endif // !TARGET_OS_OSX
#endif
#if ((TARGET_OS_IOS || TARGET_OS_TV) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= 130000))
#if TARGET_OS_IOS || TARGET_OS_TV
#define UISCENE_SUPPORTED 1
#endif

View File

@@ -23,7 +23,7 @@
#if TARGET_OS_IOS
#import <UIKit/UIKit.h>
#endif
#endif // TARGET_OS_IOS
@implementation GULAppEnvironmentUtil
@@ -167,7 +167,7 @@ static BOOL HasEmbeddedMobileProvision(void) {
model = @"watchOS Simulator";
#elif TARGET_OS_TV
model = @"tvOS Simulator";
#elif defined(TARGET_OS_VISION) && TARGET_OS_VISION
#elif TARGET_OS_VISION
model = @"visionOS Simulator";
#elif TARGET_OS_IOS
switch ([[UIDevice currentDevice] userInterfaceIdiom]) {
@@ -195,8 +195,7 @@ static BOOL HasEmbeddedMobileProvision(void) {
+ (NSString *)systemVersion {
#if TARGET_OS_IOS
return [UIDevice currentDevice].systemVersion;
#elif TARGET_OS_OSX || TARGET_OS_TV || TARGET_OS_WATCH || \
(defined(TARGET_OS_VISION) && TARGET_OS_VISION)
#elif TARGET_OS_OSX || TARGET_OS_TV || TARGET_OS_WATCH || TARGET_OS_VISION
// Assemble the systemVersion, excluding the patch version if it's 0.
NSOperatingSystemVersion osVersion = [NSProcessInfo processInfo].operatingSystemVersion;
NSMutableString *versionString = [[NSMutableString alloc]
@@ -218,22 +217,23 @@ static BOOL HasEmbeddedMobileProvision(void) {
#endif
}
+ (BOOL)isIOS7OrHigher {
return YES;
+ (BOOL)isAppClip {
#if TARGET_OS_IOS
// Documented by <a
// href="https://developer.apple.com/documentation/bundleresources/information-property-list/nsappclip">Apple</a>
// App clips have an NSAppClip entry in the top level of their Info.plist.
NSDictionary *appClipEntry = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSAppClip"];
return appClipEntry != nil;
#elif TARGET_OS_OSX || TARGET_OS_TV || TARGET_OS_WATCH || TARGET_OS_VISION
return NO;
#endif
}
+ (BOOL)hasSwiftRuntime {
// The class
// [Swift._SwiftObject](https://github.com/apple/swift/blob/5eac3e2818eb340b11232aff83edfbd1c307fa03/stdlib/public/runtime/SwiftObject.h#L35)
// is a part of Swift runtime, so it should be present if Swift runtime is available.
BOOL hasSwiftRuntime =
objc_lookUpClass("Swift._SwiftObject") != nil ||
// Swift object class name before
// https://github.com/apple/swift/commit/9637b4a6e11ddca72f5f6dbe528efc7c92f14d01
objc_getClass("_TtCs12_SwiftObject") != nil;
return hasSwiftRuntime;
+ (BOOL)supportsBackgroundURLSessionUploads {
// Neither app extensions nor App Clips support background uploads.
BOOL isExtension = self.isAppExtension;
BOOL isAppClip = self.isAppClip;
return !(isExtension || isAppClip);
}
+ (NSString *)applePlatform {
@@ -243,8 +243,7 @@ static BOOL HasEmbeddedMobileProvision(void) {
// `true`, which means the condition list is order-sensitive.
#if TARGET_OS_MACCATALYST
applePlatform = @"maccatalyst";
#elif TARGET_OS_IOS && (!defined(TARGET_OS_VISION) || !TARGET_OS_VISION)
#if defined(__IPHONE_14_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 140000
#elif TARGET_OS_IOS
if (@available(iOS 14.0, *)) {
// Early iOS 14 betas do not include isiOSAppOnMac (#6969)
applePlatform = ([[NSProcessInfo processInfo] respondsToSelector:@selector(isiOSAppOnMac)] &&
@@ -254,17 +253,13 @@ static BOOL HasEmbeddedMobileProvision(void) {
} else {
applePlatform = @"ios";
}
#else // defined(__IPHONE_14_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 140000
applePlatform = @"ios";
#endif // defined(__IPHONE_14_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 140000
#elif TARGET_OS_TV
applePlatform = @"tvos";
#elif TARGET_OS_OSX
applePlatform = @"macos";
#elif TARGET_OS_WATCH
applePlatform = @"watchos";
#elif defined(TARGET_OS_VISION) && TARGET_OS_VISION
#elif TARGET_OS_VISION
applePlatform = @"visionos";
#endif // TARGET_OS_MACCATALYST

View File

@@ -1,153 +0,0 @@
/*
* 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 "GoogleUtilities/Environment/Public/GoogleUtilities/GULHeartbeatDateStorage.h"
#import "GoogleUtilities/Environment/Public/GoogleUtilities/GULSecureCoding.h"
NSString *const kGULHeartbeatStorageDirectory = @"Google/FIRApp";
@interface GULHeartbeatDateStorage ()
/** The name of the file that stores heartbeat information. */
@property(nonatomic, readonly) NSString *fileName;
@end
@implementation GULHeartbeatDateStorage
@synthesize fileURL = _fileURL;
- (instancetype)initWithFileName:(NSString *)fileName {
if (fileName == nil) return nil;
self = [super init];
if (self) {
_fileName = fileName;
}
return self;
}
/** Lazy getter for fileURL.
* @return fileURL where heartbeat information is stored.
*/
- (NSURL *)fileURL {
if (!_fileURL) {
NSURL *directoryURL = [self directoryPathURL];
[self checkAndCreateDirectory:directoryURL];
_fileURL = [directoryURL URLByAppendingPathComponent:_fileName];
}
return _fileURL;
}
/** Returns the URL path of the directory for heartbeat storage data.
* @return the URL path of the directory for heartbeat storage data.
*/
- (NSURL *)directoryPathURL {
NSArray<NSString *> *paths;
#if TARGET_OS_TV
paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
#else
paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES);
#endif // TARGET_OS_TV
NSString *rootPath = [paths lastObject];
NSURL *rootURL = [NSURL fileURLWithPath:rootPath];
NSURL *directoryURL = [rootURL URLByAppendingPathComponent:kGULHeartbeatStorageDirectory];
return directoryURL;
}
/** Check for the existence of the directory specified by the URL, and create it if it does not
* exist.
* @param directoryPathURL The path to the directory that needs to exist.
*/
- (void)checkAndCreateDirectory:(NSURL *)directoryPathURL {
NSError *error;
if (![directoryPathURL checkResourceIsReachableAndReturnError:&error]) {
NSError *error;
[[NSFileManager defaultManager] createDirectoryAtURL:directoryPathURL
withIntermediateDirectories:YES
attributes:nil
error:&error];
}
}
- (nullable NSDate *)heartbeatDateForTag:(NSString *)tag {
@synchronized(self.class) {
NSDictionary *heartbeatDictionary = [self heartbeatDictionaryWithFileURL:self.fileURL];
NSDate *heartbeatDate = heartbeatDictionary[tag];
// Validate the value type. If the storage file was corrupted or updated with a different format
// by a newer SDK version the value type may be different.
if (![heartbeatDate isKindOfClass:[NSDate class]]) {
heartbeatDate = nil;
}
return heartbeatDate;
}
}
- (BOOL)setHearbeatDate:(NSDate *)date forTag:(NSString *)tag {
// Synchronize on the class to ensure that the different instances of the class will not access
// the same file concurrently.
// TODO: Consider a different synchronization strategy here and in `-heartbeatDateForTag:` method.
// Currently no heartbeats can be read/written concurrently even if they are in different files.
@synchronized(self.class) {
NSMutableDictionary *heartbeatDictionary =
[[self heartbeatDictionaryWithFileURL:self.fileURL] mutableCopy];
heartbeatDictionary[tag] = date;
NSError *error;
BOOL isSuccess = [self writeDictionary:[heartbeatDictionary copy]
forWritingURL:self.fileURL
error:&error];
return isSuccess;
}
}
- (NSDictionary *)heartbeatDictionaryWithFileURL:(NSURL *)readingFileURL {
NSDictionary *heartbeatDictionary;
NSError *error;
NSData *objectData = [NSData dataWithContentsOfURL:readingFileURL options:0 error:&error];
if (objectData.length > 0 && error == nil) {
NSSet<Class> *objectClasses =
[NSSet setWithArray:@[ NSDictionary.class, NSDate.class, NSString.class ]];
heartbeatDictionary = [GULSecureCoding unarchivedObjectOfClasses:objectClasses
fromData:objectData
error:&error];
}
if (heartbeatDictionary.count == 0 || error != nil) {
heartbeatDictionary = [NSDictionary dictionary];
}
return heartbeatDictionary;
}
- (BOOL)writeDictionary:(NSDictionary *)dictionary
forWritingURL:(NSURL *)writingFileURL
error:(NSError **)outError {
// Archive a mutable copy `dictionary` for writing to disk. This is done for
// backwards compatibility. See Google Utilities issue #36 for more context.
// TODO: Remove usage of mutable copy in a future version of Google Utilities.
NSData *data = [GULSecureCoding archivedDataWithRootObject:[dictionary mutableCopy]
error:outError];
if (data.length == 0) {
return NO;
}
return [data writeToURL:writingFileURL atomically:YES];
}
@end

View File

@@ -1,68 +0,0 @@
/*
* Copyright 2021 Google LLC
*
* 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 "GoogleUtilities/Environment/Public/GoogleUtilities/GULHeartbeatDateStorageUserDefaults.h"
@interface GULHeartbeatDateStorageUserDefaults ()
/** The storage to store the date of the last sent heartbeat. */
@property(nonatomic, readonly) NSUserDefaults *userDefaults;
/** The key for user defaults to store heartbeat information. */
@property(nonatomic, readonly) NSString *key;
@end
@implementation GULHeartbeatDateStorageUserDefaults
- (instancetype)initWithDefaults:(NSUserDefaults *)defaults key:(NSString *)key {
self = [super init];
if (self) {
_userDefaults = defaults;
_key = key;
}
return self;
}
- (NSMutableDictionary *)heartbeatDictionaryFromDefaults {
NSDictionary *heartbeatDict = [self.userDefaults objectForKey:self.key];
if (heartbeatDict != nil) {
return [heartbeatDict mutableCopy];
} else {
return [NSMutableDictionary dictionary];
}
}
- (nullable NSDate *)heartbeatDateForTag:(NSString *)tag {
NSDate *date = nil;
@synchronized(self.userDefaults) {
NSMutableDictionary *dict = [self heartbeatDictionaryFromDefaults];
date = dict[tag];
}
return date;
}
- (BOOL)setHearbeatDate:(NSDate *)date forTag:(NSString *)tag {
@synchronized(self.userDefaults) {
NSMutableDictionary *dict = [self heartbeatDictionaryFromDefaults];
dict[tag] = date;
[self.userDefaults setObject:dict forKey:self.key];
}
return true;
}
@end

View File

@@ -1,103 +0,0 @@
// 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 "GoogleUtilities/Environment/Public/GoogleUtilities/GULSecureCoding.h"
NSString *const kGULSecureCodingError = @"GULSecureCodingError";
@implementation GULSecureCoding
+ (nullable id)unarchivedObjectOfClasses:(NSSet<Class> *)classes
fromData:(NSData *)data
error:(NSError **)outError {
id object;
#if __has_builtin(__builtin_available)
if (@available(macOS 10.13, iOS 11.0, tvOS 11.0, watchOS 4.0, *)) {
object = [NSKeyedUnarchiver unarchivedObjectOfClasses:classes fromData:data error:outError];
} else
#endif // __has_builtin(__builtin_available)
{
@try {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
#pragma clang diagnostic pop
unarchiver.requiresSecureCoding = YES;
object = [unarchiver decodeObjectOfClasses:classes forKey:NSKeyedArchiveRootObjectKey];
} @catch (NSException *exception) {
if (outError) {
*outError = [self archivingErrorWithException:exception];
}
}
if (object == nil && outError && *outError == nil) {
NSString *failureReason = @"NSKeyedUnarchiver failed to unarchive data.";
*outError = [NSError errorWithDomain:kGULSecureCodingError
code:-1
userInfo:@{NSLocalizedFailureReasonErrorKey : failureReason}];
}
}
return object;
}
+ (nullable id)unarchivedObjectOfClass:(Class)class
fromData:(NSData *)data
error:(NSError **)outError {
return [self unarchivedObjectOfClasses:[NSSet setWithObject:class] fromData:data error:outError];
}
+ (nullable NSData *)archivedDataWithRootObject:(id<NSCoding>)object error:(NSError **)outError {
NSData *archiveData;
#if __has_builtin(__builtin_available)
if (@available(macOS 10.13, iOS 11.0, tvOS 11.0, watchOS 4.0, *)) {
archiveData = [NSKeyedArchiver archivedDataWithRootObject:object
requiringSecureCoding:YES
error:outError];
} else
#endif // __has_builtin(__builtin_available)
{
@try {
NSMutableData *data = [NSMutableData data];
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
#pragma clang diagnostic pop
archiver.requiresSecureCoding = YES;
[archiver encodeObject:object forKey:NSKeyedArchiveRootObjectKey];
[archiver finishEncoding];
archiveData = [data copy];
} @catch (NSException *exception) {
if (outError) {
*outError = [self archivingErrorWithException:exception];
}
}
}
return archiveData;
}
+ (NSError *)archivingErrorWithException:(NSException *)exception {
NSString *failureReason = [NSString
stringWithFormat:@"NSKeyedArchiver exception with name: %@, reason: %@, userInfo: %@",
exception.name, exception.reason, exception.userInfo];
NSDictionary *errorUserInfo = @{NSLocalizedFailureReasonErrorKey : failureReason};
return [NSError errorWithDomain:kGULSecureCodingError code:-1 userInfo:errorUserInfo];
}
@end

View File

@@ -20,7 +20,6 @@
#if __has_include("CoreTelephony/CTTelephonyNetworkInfo.h") && !TARGET_OS_MACCATALYST && \
!TARGET_OS_OSX && !TARGET_OS_TV && !TARGET_OS_WATCH
#define TARGET_HAS_MOBILE_CONNECTIVITY
#import <CoreTelephony/CTCarrier.h>
#import <CoreTelephony/CTTelephonyNetworkInfo.h>
#import <SystemConfiguration/SystemConfiguration.h>
#endif
@@ -38,61 +37,6 @@
}
#endif
+ (NSString *_Nullable)getNetworkMobileCountryCode {
#ifdef TARGET_HAS_MOBILE_CONNECTIVITY
CTTelephonyNetworkInfo *networkInfo = [GULNetworkInfo getNetworkInfo];
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
CTCarrier *provider = networkInfo.subscriberCellularProvider;
#pragma clang diagnostic push
return provider.mobileCountryCode;
#endif
return nil;
}
+ (NSString *_Nullable)getNetworkMobileNetworkCode {
#ifdef TARGET_HAS_MOBILE_CONNECTIVITY
CTTelephonyNetworkInfo *networkInfo = [GULNetworkInfo getNetworkInfo];
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
CTCarrier *provider = networkInfo.subscriberCellularProvider;
#pragma clang diagnostic push
return provider.mobileNetworkCode;
#endif
return nil;
}
/**
* Returns the formatted MccMnc if the inputs are valid, otherwise nil
* @param mcc The Mobile Country Code returned from `getNetworkMobileCountryCode`
* @param mnc The Mobile Network Code returned from `getNetworkMobileNetworkCode`
* @returns A string with the concatenated mccMnc if both inputs are valid, otherwise nil
*/
+ (NSString *_Nullable)formatMcc:(NSString *)mcc andMNC:(NSString *)mnc {
// These are both nil if the target does not support mobile connectivity
if (mcc == nil && mnc == nil) {
return nil;
}
if (mcc.length != 3 || mnc.length < 2 || mnc.length > 3) {
return nil;
}
// If the resulting appended mcc + mnc contains characters that are not
// decimal digits, return nil
static NSCharacterSet *notDigits;
static dispatch_once_t token;
dispatch_once(&token, ^{
notDigits = [[NSCharacterSet decimalDigitCharacterSet] invertedSet];
});
NSString *mccMnc = [mcc stringByAppendingString:mnc];
if ([mccMnc rangeOfCharacterFromSet:notDigits].location != NSNotFound) {
return nil;
}
return mccMnc;
}
+ (GULNetworkType)getNetworkType {
GULNetworkType networkType = GULNetworkTypeNone;
@@ -126,13 +70,11 @@
+ (NSString *)getNetworkRadioType {
#ifdef TARGET_HAS_MOBILE_CONNECTIVITY
CTTelephonyNetworkInfo *networkInfo = [GULNetworkInfo getNetworkInfo];
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
return networkInfo.currentRadioAccessTechnology;
#pragma clang diagnostic pop
#else
return @"";
if (networkInfo.serviceCurrentRadioAccessTechnology.count) {
return networkInfo.serviceCurrentRadioAccessTechnology.allValues[0] ?: @"";
}
#endif
return @"";
}
@end

View File

@@ -46,12 +46,12 @@ NS_ASSUME_NONNULL_BEGIN
/// Indicates whether it is running inside an extension or an app.
+ (BOOL)isAppExtension;
/// @return Returns @YES when is run on iOS version greater or equal to 7.0
+ (BOOL)isIOS7OrHigher DEPRECATED_MSG_ATTRIBUTE(
"Always `YES` because only iOS 8 and higher supported. The method will be removed.");
/// Indicates whether it is running inside an app clip or a full app.
+ (BOOL)isAppClip;
/// @return YES if Swift runtime detected in the app.
+ (BOOL)hasSwiftRuntime __deprecated;
/// Indicates whether the current target supports background URL session uploads.
/// App extensions and app clips do not support background URL sessions.
+ (BOOL)supportsBackgroundURLSessionUploads;
/// @return An Apple platform. Possible values "ios", "tvos", "macos", "watchos", "maccatalyst", and
/// "visionos".

View File

@@ -1,40 +0,0 @@
/*
* Copyright 2021 Google LLC
*
* 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 <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/**
* Describes an object that can store and fetch heartbeat dates for given tags.
*/
@protocol GULHeartbeatDateStorable <NSObject>
/**
* Reads the date from the specified file for the given tag.
* @return Returns date if exists, otherwise `nil`.
*/
- (nullable NSDate *)heartbeatDateForTag:(NSString *)tag;
/**
* Saves the date for the specified tag in the specified file.
* @return YES on success, NO otherwise.
*/
- (BOOL)setHearbeatDate:(NSDate *)date forTag:(NSString *)tag;
@end
NS_ASSUME_NONNULL_END

View File

@@ -1,54 +0,0 @@
/*
* 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 <Foundation/Foundation.h>
#import "GULHeartbeatDateStorable.h"
NS_ASSUME_NONNULL_BEGIN
/// The name of the directory where the heartbeat data is stored.
extern NSString *const kGULHeartbeatStorageDirectory;
/// Stores either a date or a dictionary to a specified file.
@interface GULHeartbeatDateStorage : NSObject <GULHeartbeatDateStorable>
- (instancetype)init NS_UNAVAILABLE;
@property(nonatomic, readonly) NSURL *fileURL;
/**
* Default initializer.
* @param fileName The name of the file to store the date information.
* exist, it will be created if needed.
*/
- (instancetype)initWithFileName:(NSString *)fileName;
/**
* Reads the date from the specified file for the given tag.
* @return Returns date if exists, otherwise `nil`.
*/
- (nullable NSDate *)heartbeatDateForTag:(NSString *)tag;
/**
* Saves the date for the specified tag in the specified file.
* @return YES on success, NO otherwise.
*/
- (BOOL)setHearbeatDate:(NSDate *)date forTag:(NSString *)tag;
@end
NS_ASSUME_NONNULL_END

View File

@@ -1,51 +0,0 @@
/*
* Copyright 2021 Google LLC
*
* 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 <Foundation/Foundation.h>
#import "GULHeartbeatDateStorable.h"
NS_ASSUME_NONNULL_BEGIN
/// Stores either a date or a dictionary to a specified file.
@interface GULHeartbeatDateStorageUserDefaults : NSObject <GULHeartbeatDateStorable>
/**
* Default initializer. tvOS can only write to the cache directory and
* there are no guarantees that the directory will persist. User defaults will
* be retained, so that should be used instead.
* @param defaults User defaults instance to store the heartbeat information.
* @param key The key to be used with the user defaults instance.
*/
- (instancetype)initWithDefaults:(NSUserDefaults *)defaults key:(NSString *)key;
- (instancetype)init NS_UNAVAILABLE;
/**
* Reads the date from the specified file for the given tag.
* @return Returns date if exists, otherwise `nil`.
*/
- (nullable NSDate *)heartbeatDateForTag:(NSString *)tag;
/**
* Saves the date for the specified tag in the specified file.
* @return YES on success, NO otherwise.
*/
- (BOOL)setHearbeatDate:(NSDate *)date forTag:(NSString *)tag;
@end
NS_ASSUME_NONNULL_END

View File

@@ -16,8 +16,6 @@
#import <Foundation/Foundation.h>
@class FBLPromise<ValueType>;
NS_ASSUME_NONNULL_BEGIN
/// The class provides a convenient, multiplatform abstraction of the Keychain.
@@ -34,42 +32,46 @@ NS_ASSUME_NONNULL_BEGIN
*/
- (instancetype)initWithService:(NSString *)service;
/**
* Get an object by key.
* @param key The key.
* @param objectClass The expected object class required by `NSSecureCoding`.
* @param accessGroup The Keychain Access Group.
*
* @return Returns a promise. It is resolved with an object stored by key if exists. It is resolved
* with `nil` when the object not found. It fails on a Keychain error.
*/
- (FBLPromise<id<NSSecureCoding>> *)getObjectForKey:(NSString *)key
objectClass:(Class)objectClass
accessGroup:(nullable NSString *)accessGroup;
/// Get an object by key.
/// @param key The key.
/// @param objectClass The expected object class required by `NSSecureCoding`.
/// @param accessGroup The Keychain Access Group.
/// @param completionHandler The completion handler to call when the
/// synchronized keychain read is complete. An error is passed to the
/// completion handler if the keychain read fails. Else, the object stored in
/// the keychain, or `nil` if it does not exist, is passed to the completion
/// handler.
- (void)getObjectForKey:(NSString *)key
objectClass:(Class)objectClass
accessGroup:(nullable NSString *)accessGroup
completionHandler:
(void (^)(id<NSSecureCoding> _Nullable obj, NSError *_Nullable error))completionHandler;
/**
* Saves the given object by the given key.
* @param object The object to store.
* @param key The key to store the object. If there is an existing object by the key, it will be
* overridden.
* @param accessGroup The Keychain Access Group.
*
* @return Returns which is resolved with `[NSNull null]` on success.
*/
- (FBLPromise<NSNull *> *)setObject:(id<NSSecureCoding>)object
forKey:(NSString *)key
accessGroup:(nullable NSString *)accessGroup;
/// Saves the given object by the given key.
/// @param object The object to store.
/// @param key The key to store the object. If there is an existing object by the key, it will be
/// overridden.
/// @param accessGroup The Keychain Access Group.
/// @param completionHandler The completion handler to call when the
/// synchronized keychain write is complete. An error is passed to the
/// completion handler if the keychain read fails. Else, the object written to
/// the keychain is passed to the completion handler.
- (void)setObject:(id<NSSecureCoding>)object
forKey:(NSString *)key
accessGroup:(nullable NSString *)accessGroup
completionHandler:
(void (^)(id<NSSecureCoding> _Nullable obj, NSError *_Nullable error))completionHandler;
/**
* Removes the object by the given key.
* @param key The key to store the object. If there is an existing object by the key, it will be
* overridden.
* @param accessGroup The Keychain Access Group.
*
* @return Returns which is resolved with `[NSNull null]` on success.
*/
- (FBLPromise<NSNull *> *)removeObjectForKey:(NSString *)key
accessGroup:(nullable NSString *)accessGroup;
/// Removes the object by the given key.
/// @param key The key to store the object. If there is an existing object by
/// the key, it will be overridden.
/// @param accessGroup The Keychain Access Group.
/// @param completionHandler The completion handler to call when the
/// synchronized keychain removal is complete. An error is passed to the
/// completion handler if the keychain removal fails.
- (void)removeObjectForKey:(NSString *)key
accessGroup:(nullable NSString *)accessGroup
completionHandler:(void (^)(NSError *_Nullable error))completionHandler;
#if TARGET_OS_OSX
/// If not `nil`, then only this keychain will be used to save and read data (see

View File

@@ -27,20 +27,6 @@ typedef NS_ENUM(NSInteger, GULNetworkType) {
/// Collection of utilities to read network status information
@interface GULNetworkInfo : NSObject
/// Returns the cellular mobile country code (mcc) if CoreTelephony is supported, otherwise nil
+ (NSString *_Nullable)getNetworkMobileCountryCode;
/// Returns the cellular mobile network code (mnc) if CoreTelephony is supported, otherwise nil
+ (NSString *_Nullable)getNetworkMobileNetworkCode;
/**
* Returns the formatted MccMnc if the inputs are valid, otherwise nil
* @param mcc The Mobile Country Code returned from `getNetworkMobileCountryCode`
* @param mnc The Mobile Network Code returned from `getNetworkMobileNetworkCode`
* @returns A string with the concatenated mccMnc if both inputs are valid, otherwise nil
*/
+ (NSString *_Nullable)formatMcc:(NSString *_Nullable)mcc andMNC:(NSString *_Nullable)mnc;
/// Returns an enum indicating the network type. The enum values should be easily transferrable to
/// the NetworkType value in android/play/playlog/proto/clientanalytics.proto. Right now this always
/// returns None on platforms other than iOS. This should be updated in the future to return Wi-Fi

View File

@@ -1,36 +0,0 @@
// 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 <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/** The class wraps `NSKeyedArchiver` and `NSKeyedUnarchiver` API to provide a unified secure coding
* methods for iOS versions before and after 11.
*/
@interface GULSecureCoding : NSObject
+ (nullable id)unarchivedObjectOfClasses:(NSSet<Class> *)classes
fromData:(NSData *)data
error:(NSError **)outError;
+ (nullable id)unarchivedObjectOfClass:(Class)class
fromData:(NSData *)data
error:(NSError **)outError;
+ (nullable NSData *)archivedDataWithRootObject:(id<NSCoding>)object error:(NSError **)outError;
@end
NS_ASSUME_NONNULL_END

View File

@@ -1,31 +0,0 @@
/*
* Copyright 2020 Google LLC
*
* 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 <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/** The class represents HTTP response received from `NSURLSession`. */
@interface GULURLSessionDataResponse : NSObject
@property(nonatomic, readonly) NSHTTPURLResponse *HTTPResponse;
@property(nonatomic, nullable, readonly) NSData *HTTPBody;
- (instancetype)initWithResponse:(NSHTTPURLResponse *)response HTTPBody:(nullable NSData *)body;
@end
NS_ASSUME_NONNULL_END

View File

@@ -1,37 +0,0 @@
/*
* Copyright 2020 Google LLC
*
* 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 <Foundation/Foundation.h>
@class FBLPromise<Value>;
@class GULURLSessionDataResponse;
NS_ASSUME_NONNULL_BEGIN
/** Promise based API for `NSURLSession`. */
@interface NSURLSession (GULPromises)
/** Creates a promise wrapping `-[NSURLSession dataTaskWithRequest:completionHandler:]` method.
* @param URLRequest The request to create a data task with.
* @return A promise that is fulfilled when an HTTP response is received (with any response code),
* or is rejected with the error passed to the task completion.
*/
- (FBLPromise<GULURLSessionDataResponse *> *)gul_dataTaskPromiseWithRequest:
(NSURLRequest *)URLRequest;
@end
NS_ASSUME_NONNULL_END

View File

@@ -17,14 +17,7 @@
#import "GoogleUtilities/Environment/Public/GoogleUtilities/GULKeychainStorage.h"
#import <Security/Security.h>
#if __has_include(<FBLPromises/FBLPromises.h>)
#import <FBLPromises/FBLPromises.h>
#else
#import "FBLPromises.h"
#endif
#import "GoogleUtilities/Environment/Public/GoogleUtilities/GULKeychainUtils.h"
#import "GoogleUtilities/Environment/Public/GoogleUtilities/GULSecureCoding.h"
@interface GULKeychainStorage ()
@property(nonatomic, readonly) dispatch_queue_t keychainQueue;
@@ -57,108 +50,113 @@
#pragma mark - Public
- (FBLPromise<id<NSSecureCoding>> *)getObjectForKey:(NSString *)key
objectClass:(Class)objectClass
accessGroup:(nullable NSString *)accessGroup {
return [FBLPromise onQueue:self.inMemoryCacheQueue
do:^id _Nullable {
// Return cached object or fail otherwise.
id object = [self.inMemoryCache objectForKey:key];
return object
?: [[NSError alloc]
initWithDomain:FBLPromiseErrorDomain
code:FBLPromiseErrorCodeValidationFailure
userInfo:nil];
}]
.recover(^id _Nullable(NSError *error) {
// Look for the object in the keychain.
return [self getObjectFromKeychainForKey:key
objectClass:objectClass
accessGroup:accessGroup];
});
- (void)getObjectForKey:(NSString *)key
objectClass:(Class)objectClass
accessGroup:(nullable NSString *)accessGroup
completionHandler:
(void (^)(id<NSSecureCoding> _Nullable obj, NSError *_Nullable error))completionHandler {
dispatch_async(self.inMemoryCacheQueue, ^{
// Return cached object or fail otherwise.
id object = [self.inMemoryCache objectForKey:key];
if (object) {
completionHandler(object, nil);
} else {
// Look for the object in the keychain.
[self getObjectFromKeychainForKey:key
objectClass:objectClass
accessGroup:accessGroup
completionHandler:completionHandler];
}
});
}
- (FBLPromise<NSNull *> *)setObject:(id<NSSecureCoding>)object
forKey:(NSString *)key
accessGroup:(nullable NSString *)accessGroup {
return [FBLPromise onQueue:self.inMemoryCacheQueue
do:^id _Nullable {
// Save to the in-memory cache first.
[self.inMemoryCache setObject:object forKey:[key copy]];
return [NSNull null];
}]
.thenOn(self.keychainQueue, ^id(id result) {
// Then store the object to the keychain.
NSDictionary *query = [self keychainQueryWithKey:key accessGroup:accessGroup];
NSError *error;
NSData *encodedObject = [GULSecureCoding archivedDataWithRootObject:object error:&error];
if (!encodedObject) {
return error;
}
- (void)setObject:(id<NSSecureCoding>)object
forKey:(NSString *)key
accessGroup:(nullable NSString *)accessGroup
completionHandler:
(void (^)(id<NSSecureCoding> _Nullable obj, NSError *_Nullable error))completionHandler {
dispatch_async(self.inMemoryCacheQueue, ^{
// Save to the in-memory cache first.
[self.inMemoryCache setObject:object forKey:[key copy]];
if (![GULKeychainUtils setItem:encodedObject withQuery:query error:&error]) {
return error;
}
dispatch_async(self.keychainQueue, ^{
// Then store the object to the keychain.
NSDictionary *query = [self keychainQueryWithKey:key accessGroup:accessGroup];
NSError *error;
NSData *encodedObject = [NSKeyedArchiver archivedDataWithRootObject:object
requiringSecureCoding:YES
error:&error];
if (!encodedObject) {
completionHandler(nil, error);
return;
}
return [NSNull null];
});
if (![GULKeychainUtils setItem:encodedObject withQuery:query error:&error]) {
completionHandler(nil, error);
return;
}
completionHandler(object, nil);
});
});
}
- (FBLPromise<NSNull *> *)removeObjectForKey:(NSString *)key
accessGroup:(nullable NSString *)accessGroup {
return [FBLPromise onQueue:self.inMemoryCacheQueue
do:^id _Nullable {
[self.inMemoryCache removeObjectForKey:key];
return nil;
}]
.thenOn(self.keychainQueue, ^id(id result) {
NSDictionary *query = [self keychainQueryWithKey:key accessGroup:accessGroup];
- (void)removeObjectForKey:(NSString *)key
accessGroup:(nullable NSString *)accessGroup
completionHandler:(void (^)(NSError *_Nullable error))completionHandler {
dispatch_async(self.inMemoryCacheQueue, ^{
[self.inMemoryCache removeObjectForKey:key];
dispatch_async(self.keychainQueue, ^{
NSDictionary *query = [self keychainQueryWithKey:key accessGroup:accessGroup];
NSError *error;
if (![GULKeychainUtils removeItemWithQuery:query error:&error]) {
return error;
}
return [NSNull null];
});
NSError *error;
if (![GULKeychainUtils removeItemWithQuery:query error:&error]) {
completionHandler(error);
} else {
completionHandler(nil);
}
});
});
}
#pragma mark - Private
- (FBLPromise<id<NSSecureCoding>> *)getObjectFromKeychainForKey:(NSString *)key
objectClass:(Class)objectClass
accessGroup:(nullable NSString *)accessGroup {
- (void)getObjectFromKeychainForKey:(NSString *)key
objectClass:(Class)objectClass
accessGroup:(nullable NSString *)accessGroup
completionHandler:(void (^)(id<NSSecureCoding> _Nullable obj,
NSError *_Nullable error))completionHandler {
// Look for the object in the keychain.
return [FBLPromise
onQueue:self.keychainQueue
do:^id {
NSDictionary *query = [self keychainQueryWithKey:key accessGroup:accessGroup];
NSError *error;
NSData *encodedObject = [GULKeychainUtils getItemWithQuery:query error:&error];
dispatch_async(self.keychainQueue, ^{
NSDictionary *query = [self keychainQueryWithKey:key accessGroup:accessGroup];
NSError *error;
NSData *encodedObject = [GULKeychainUtils getItemWithQuery:query error:&error];
if (error) {
return error;
}
if (!encodedObject) {
return nil;
}
id object = [GULSecureCoding unarchivedObjectOfClass:objectClass
fromData:encodedObject
error:&error];
if (error) {
return error;
}
if (error) {
completionHandler(nil, error);
return;
}
if (!encodedObject) {
completionHandler(nil, nil);
return;
}
id object = [NSKeyedUnarchiver unarchivedObjectOfClass:objectClass
fromData:encodedObject
error:&error];
if (error) {
completionHandler(nil, error);
return;
}
return object;
}]
.thenOn(self.inMemoryCacheQueue,
^id<NSSecureCoding> _Nullable(id<NSSecureCoding> _Nullable object) {
// Save object to the in-memory cache if exists and return the object.
if (object) {
[self.inMemoryCache setObject:object forKey:[key copy]];
}
return object;
});
dispatch_async(self.inMemoryCacheQueue, ^{
// Save object to the in-memory cache if exists and return the object.
if (object) {
[self.inMemoryCache setObject:object forKey:[key copy]];
}
completionHandler(object, nil);
});
});
}
- (void)resetInMemoryCache {

View File

@@ -1,30 +0,0 @@
/*
* Copyright 2020 Google LLC
*
* 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 "GoogleUtilities/Environment/Public/GoogleUtilities/GULURLSessionDataResponse.h"
@implementation GULURLSessionDataResponse
- (instancetype)initWithResponse:(NSHTTPURLResponse *)response HTTPBody:(NSData *)body {
self = [super init];
if (self) {
_HTTPResponse = response;
_HTTPBody = body;
}
return self;
}
@end

View File

@@ -1,46 +0,0 @@
/*
* Copyright 2020 Google LLC
*
* 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 "GoogleUtilities/Environment/Public/GoogleUtilities/NSURLSession+GULPromises.h"
#if __has_include(<FBLPromises/FBLPromises.h>)
#import <FBLPromises/FBLPromises.h>
#else
#import "FBLPromises.h"
#endif
#import "GoogleUtilities/Environment/Public/GoogleUtilities/GULURLSessionDataResponse.h"
@implementation NSURLSession (GULPromises)
- (FBLPromise<GULURLSessionDataResponse *> *)gul_dataTaskPromiseWithRequest:
(NSURLRequest *)URLRequest {
return [FBLPromise async:^(FBLPromiseFulfillBlock fulfill, FBLPromiseRejectBlock reject) {
[[self dataTaskWithRequest:URLRequest
completionHandler:^(NSData *_Nullable data, NSURLResponse *_Nullable response,
NSError *_Nullable error) {
if (error) {
reject(error);
} else {
fulfill([[GULURLSessionDataResponse alloc]
initWithResponse:(NSHTTPURLResponse *)response
HTTPBody:data]);
}
}] resume];
}];
}
@end

View File

@@ -14,18 +14,13 @@
#import "GoogleUtilities/Logger/Public/GoogleUtilities/GULLogger.h"
#include <asl.h>
#import <os/log.h>
#import "GoogleUtilities/Environment/Public/GoogleUtilities/GULAppEnvironmentUtil.h"
#import "GoogleUtilities/Logger/Public/GoogleUtilities/GULLoggerLevel.h"
/// ASL client facility name used by GULLogger.
const char *kGULLoggerASLClientFacilityName = "com.google.utilities.logger";
static dispatch_once_t sGULLoggerOnceToken;
static aslclient sGULLoggerClient;
static dispatch_queue_t sGULClientQueue;
static BOOL sGULLoggerDebugMode;
@@ -35,42 +30,26 @@ static GULLoggerLevel sGULLoggerMaximumLevel;
// Allow clients to register a version to include in the log.
static NSString *sVersion = @"";
NSString *const kGULLogSubsystem = @"com.google.utilities.logger";
static GULLoggerService kGULLoggerLogger = @"[GULLogger]";
static NSMutableDictionary<NSString *, NSMutableDictionary<GULLoggerService, os_log_t> *>
*sGULServiceLogs;
#ifdef DEBUG
/// The regex pattern for the message code.
static NSString *const kMessageCodePattern = @"^I-[A-Z]{3}[0-9]{6}$";
static NSRegularExpression *sMessageCodeRegex;
#endif
void GULLoggerInitializeASL(void) {
void GULLoggerInitialize(void) {
dispatch_once(&sGULLoggerOnceToken, ^{
NSInteger majorOSVersion = [[GULAppEnvironmentUtil systemVersion] integerValue];
uint32_t aslOptions = ASL_OPT_STDERR;
#if TARGET_OS_SIMULATOR
// The iOS 11 simulator doesn't need the ASL_OPT_STDERR flag.
if (majorOSVersion >= 11) {
aslOptions = 0;
}
#else
// Devices running iOS 10 or higher don't need the ASL_OPT_STDERR flag.
if (majorOSVersion >= 10) {
aslOptions = 0;
}
#endif // TARGET_OS_SIMULATOR
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations" // asl is deprecated
// Initialize the ASL client handle.
sGULLoggerClient = asl_open(NULL, kGULLoggerASLClientFacilityName, aslOptions);
sGULLoggerMaximumLevel = GULLoggerLevelNotice;
// Set the filter used by system/device log. Initialize in default mode.
asl_set_filter(sGULLoggerClient, ASL_FILTER_MASK_UPTO(ASL_LEVEL_NOTICE));
sGULClientQueue = dispatch_queue_create("GULLoggingClientQueue", DISPATCH_QUEUE_SERIAL);
dispatch_set_target_queue(sGULClientQueue,
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0));
sGULServiceLogs = [NSMutableDictionary dictionary];
#ifdef DEBUG
sMessageCodeRegex = [NSRegularExpression regularExpressionWithPattern:kMessageCodePattern
options:0
@@ -79,10 +58,6 @@ void GULLoggerInitializeASL(void) {
});
}
void GULLoggerEnableSTDERR(void) {
asl_add_log_file(sGULLoggerClient, STDERR_FILENO);
}
void GULLoggerForceDebug(void) {
// We should enable debug mode if we're not running from App Store.
if (![GULAppEnvironmentUtil isFromAppStore]) {
@@ -97,27 +72,24 @@ GULLoggerLevel GULGetLoggerLevel(void) {
__attribute__((no_sanitize("thread"))) void GULSetLoggerLevel(GULLoggerLevel loggerLevel) {
if (loggerLevel < GULLoggerLevelMin || loggerLevel > GULLoggerLevelMax) {
GULLogError(kGULLoggerLogger, NO, @"I-COR000023", @"Invalid logger level, %ld",
(long)loggerLevel);
GULOSLogError(kGULLogSubsystem, kGULLoggerLogger, YES, @"I-COR000023",
@"Invalid logger level, %ld", (long)loggerLevel);
return;
}
GULLoggerInitializeASL();
GULLoggerInitialize();
// We should not raise the logger level if we are running from App Store.
if (loggerLevel >= GULLoggerLevelNotice && [GULAppEnvironmentUtil isFromAppStore]) {
return;
}
sGULLoggerMaximumLevel = loggerLevel;
dispatch_async(sGULClientQueue, ^{
asl_set_filter(sGULLoggerClient, ASL_FILTER_MASK_UPTO(loggerLevel));
});
}
/**
* Check if the level is high enough to be loggable.
*/
__attribute__((no_sanitize("thread"))) BOOL GULIsLoggableLevel(GULLoggerLevel loggerLevel) {
GULLoggerInitializeASL();
GULLoggerInitialize();
if (sGULLoggerDebugMode) {
return YES;
}
@@ -131,10 +103,6 @@ void GULResetLogger(void) {
sGULLoggerMaximumLevel = GULLoggerLevelNotice;
}
aslclient getGULLoggerClient(void) {
return sGULLoggerClient;
}
dispatch_queue_t getGULClientQueue(void) {
return sGULClientQueue;
}
@@ -148,13 +116,28 @@ void GULLoggerRegisterVersion(NSString *version) {
sVersion = version;
}
void GULLogBasic(GULLoggerLevel level,
GULLoggerService service,
BOOL forceLog,
NSString *messageCode,
NSString *message,
va_list args_ptr) {
GULLoggerInitializeASL();
os_log_type_t GULLoggerLevelToOSLogType(GULLoggerLevel level) {
switch (level) {
case GULLoggerLevelError:
return OS_LOG_TYPE_ERROR;
case GULLoggerLevelWarning:
case GULLoggerLevelNotice:
return OS_LOG_TYPE_DEFAULT;
case GULLoggerLevelInfo:
return OS_LOG_TYPE_INFO;
case GULLoggerLevelDebug:
return OS_LOG_TYPE_DEBUG;
}
}
void GULOSLogBasic(GULLoggerLevel level,
NSString *subsystem,
NSString *category,
BOOL forceLog,
NSString *messageCode,
NSString *message,
va_list args_ptr) {
GULLoggerInitialize();
if (!(level <= sGULLoggerMaximumLevel || sGULLoggerDebugMode || forceLog)) {
return;
}
@@ -172,12 +155,23 @@ void GULLogBasic(GULLoggerLevel level,
} else {
logMsg = [[NSString alloc] initWithFormat:message arguments:args_ptr];
}
logMsg = [NSString stringWithFormat:@"%@ - %@[%@] %@", sVersion, service, messageCode, logMsg];
logMsg = [NSString stringWithFormat:@"%@ - %@[%@] %@", sVersion, category, messageCode, logMsg];
dispatch_async(sGULClientQueue, ^{
asl_log(sGULLoggerClient, NULL, (int)level, "%s", logMsg.UTF8String);
NSMutableDictionary<GULLoggerService, os_log_t> *subsystemLogs = sGULServiceLogs[subsystem];
if (!subsystemLogs) {
subsystemLogs = [NSMutableDictionary dictionary];
sGULServiceLogs[subsystem] = subsystemLogs;
}
os_log_t serviceLog = [subsystemLogs objectForKey:subsystem];
if (!serviceLog) {
serviceLog = os_log_create(subsystem.UTF8String, category.UTF8String);
subsystemLogs[category] = serviceLog;
}
os_log_with_type(serviceLog, GULLoggerLevelToOSLogType(level), "%{public}@", logMsg);
});
}
#pragma clang diagnostic pop
/**
* Generates the logging functions using macros.
@@ -187,13 +181,14 @@ void GULLogBasic(GULLoggerLevel level,
* Calling GULLogDebug({service}, @"I-XYZ000001", @"Configure succeed.") shows:
* yyyy-mm-dd hh:mm:ss.SSS sender[PID] <Debug> [{service}][I-XYZ000001] Configure succeed.
*/
#define GUL_LOGGING_FUNCTION(level) \
void GULLog##level(GULLoggerService service, BOOL force, NSString *messageCode, \
NSString *message, ...) { \
va_list args_ptr; \
va_start(args_ptr, message); \
GULLogBasic(GULLoggerLevel##level, service, force, messageCode, message, args_ptr); \
va_end(args_ptr); \
#define GUL_LOGGING_FUNCTION(level) \
void GULOSLog##level(NSString *subsystem, NSString *category, BOOL force, NSString *messageCode, \
NSString *message, ...) { \
va_list args_ptr; \
va_start(args_ptr, message); \
GULOSLogBasic(GULLoggerLevel##level, subsystem, category, force, messageCode, message, \
args_ptr); \
va_end(args_ptr); \
}
GUL_LOGGING_FUNCTION(Error)
@@ -202,18 +197,27 @@ GUL_LOGGING_FUNCTION(Notice)
GUL_LOGGING_FUNCTION(Info)
GUL_LOGGING_FUNCTION(Debug)
#undef GUL_MAKE_LOGGER
#undef GUL_LOGGING_FUNCTION
#pragma mark - GULLoggerWrapper
@implementation GULLoggerWrapper
+ (void)logWithLevel:(GULLoggerLevel)level
subsystem:(NSString *)subsystem
category:(GULLoggerService)category
messageCode:(NSString *)messageCode
message:(NSString *)message
arguments:(va_list)args {
GULOSLogBasic(level, subsystem, category, NO, messageCode, message, args);
}
+ (void)logWithLevel:(GULLoggerLevel)level
withService:(GULLoggerService)service
withCode:(NSString *)messageCode
withMessage:(NSString *)message
withArgs:(va_list)args {
GULLogBasic(level, service, NO, messageCode, message, args);
GULOSLogBasic(level, kGULLogSubsystem, service, NO, messageCode, message, args);
}
@end

View File

@@ -22,6 +22,8 @@ NS_ASSUME_NONNULL_BEGIN
/**
* The services used in the logger.
*
* DEPRECATED; use NSString instead.
*/
typedef NSString *const GULLoggerService;
@@ -29,24 +31,16 @@ typedef NSString *const GULLoggerService;
extern "C" {
#endif // __cplusplus
/**
* Initialize GULLogger.
*/
extern void GULLoggerInitializeASL(void);
/// Used for other GoogleUtilities logging.
extern NSString *const kGULLogSubsystem;
/**
* Override log level to Debug.
*/
/// Initialize GULLogger.
extern void GULLoggerInitialize(void);
/// Override log level to Debug.
void GULLoggerForceDebug(void);
/**
* Turn on logging to STDERR.
*/
extern void GULLoggerEnableSTDERR(void);
/**
* Gets the current GULLoggerLevel.
*/
/// Gets the current `GULLoggerLevel`.
extern GULLoggerLevel GULGetLoggerLevel(void);
/**
@@ -81,17 +75,18 @@ extern void GULLoggerRegisterVersion(NSString *version);
* (optional) variable arguments list obtained from calling va_start, used when message is a format
* string.
*/
extern void GULLogBasic(GULLoggerLevel level,
GULLoggerService service,
BOOL forceLog,
NSString *messageCode,
NSString *message,
extern void GULOSLogBasic(GULLoggerLevel level,
NSString *subsystem,
NSString *category,
BOOL forceLog,
NSString *messageCode,
NSString *message,
// On 64-bit simulators, va_list is not a pointer, so cannot be marked nullable
// See: http://stackoverflow.com/q/29095469
#if __LP64__ && TARGET_OS_SIMULATOR || TARGET_OS_OSX
va_list args_ptr
va_list args_ptr
#else
va_list _Nullable args_ptr
va_list _Nullable args_ptr
#endif
);
@@ -108,31 +103,36 @@ extern void GULLogBasic(GULLoggerLevel level,
* Example usage:
* GULLogError(kGULLoggerCore, @"I-COR000001", @"Configuration of %@ failed.", app.name);
*/
extern void GULLogError(GULLoggerService service,
BOOL force,
NSString *messageCode,
NSString *message,
...) NS_FORMAT_FUNCTION(4, 5);
extern void GULLogWarning(GULLoggerService service,
extern void GULOSLogError(NSString *subsystem,
GULLoggerService category,
BOOL force,
NSString *messageCode,
NSString *message,
...) NS_FORMAT_FUNCTION(4, 5);
extern void GULLogNotice(GULLoggerService service,
...) NS_FORMAT_FUNCTION(5, 6);
extern void GULOSLogWarning(NSString *subsystem,
GULLoggerService category,
BOOL force,
NSString *messageCode,
NSString *message,
...) NS_FORMAT_FUNCTION(5, 6);
extern void GULOSLogNotice(NSString *subsystem,
GULLoggerService category,
BOOL force,
NSString *messageCode,
NSString *message,
...) NS_FORMAT_FUNCTION(5, 6);
extern void GULOSLogInfo(NSString *subsystem,
GULLoggerService category,
BOOL force,
NSString *messageCode,
NSString *message,
...) NS_FORMAT_FUNCTION(4, 5);
extern void GULLogInfo(GULLoggerService service,
BOOL force,
NSString *messageCode,
NSString *message,
...) NS_FORMAT_FUNCTION(4, 5);
extern void GULLogDebug(GULLoggerService service,
BOOL force,
NSString *messageCode,
NSString *message,
...) NS_FORMAT_FUNCTION(4, 5);
...) NS_FORMAT_FUNCTION(5, 6);
extern void GULOSLogDebug(NSString *subsystem,
GULLoggerService category,
BOOL force,
NSString *messageCode,
NSString *message,
...) NS_FORMAT_FUNCTION(5, 6);
#ifdef __cplusplus
} // extern "C"
@@ -140,24 +140,25 @@ extern void GULLogDebug(GULLoggerService service,
@interface GULLoggerWrapper : NSObject
/**
* Objective-C wrapper for GULLogBasic to allow weak linking to GULLogger
* (required) log level (one of the GULLoggerLevel enum values).
* (required) service name of type GULLoggerService.
* (required) message code starting with "I-" which means iOS, followed by a capitalized
* three-character service identifier and a six digit integer message ID that is unique
* within the service.
* An example of the message code is @"I-COR000001".
* (required) message string which can be a format string.
* (optional) variable arguments list obtained from calling va_start, used when message is a format
* string.
*/
/// Objective-C wrapper for `GULOSLogBasic` to allow weak linking to `GULLogger`.
///
/// - Parameters:
/// - level: The log level (one of the `GULLoggerLevel` enum values).
/// - subsystem: An identifier for the subsystem performing logging, e.g., `com.example.logger`.
/// - category: The category name within the `subsystem` to group related messages, e.g.,
/// `[GoogleUtilities/Example]`.
/// - messageCode: The message code starting with "I-" which means iOS, followed by a capitalized
/// three-character service identifier and a six digit integer message ID that is unique within
/// the service. An example of the message code is @"I-COR000001".
/// - message: The message to log, which may be a format string.
/// - args: The variable arguments list obtained from calling va_start, used when message is
/// a format string; optional if `message` is not a format string.
+ (void)logWithLevel:(GULLoggerLevel)level
withService:(GULLoggerService)service
withCode:(NSString *)messageCode
withMessage:(NSString *)message
withArgs:(va_list)args;
subsystem:(NSString *)subsystem
category:(NSString *)category
messageCode:(NSString *)messageCode
message:(NSString *)message
arguments:(va_list)args;
@end

View File

@@ -16,22 +16,32 @@
#import <Foundation/Foundation.h>
/**
* The log levels used by internal logging.
*/
NS_ASSUME_NONNULL_BEGIN
/// The log levels used by internal logging.
typedef NS_ENUM(NSInteger, GULLoggerLevel) {
/** Error level, matches ASL_LEVEL_ERR. */
GULLoggerLevelError = 3,
/** Warning level, matches ASL_LEVEL_WARNING. */
GULLoggerLevelWarning = 4,
/** Notice level, matches ASL_LEVEL_NOTICE. */
GULLoggerLevelNotice = 5,
/** Info level, matches ASL_LEVEL_INFO. */
GULLoggerLevelInfo = 6,
/** Debug level, matches ASL_LEVEL_DEBUG. */
GULLoggerLevelDebug = 7,
/** Minimum log level. */
/// Error level, corresponding to `OS_LOG_TYPE_ERROR`.
GULLoggerLevelError = 3, // For backwards compatibility, the enum value matches `ASL_LEVEL_ERR`.
/// Warning level, corresponding to `OS_LOG_TYPE_DEFAULT`.
///
/// > Note: Since OSLog doesn't have a WARNING type, this is equivalent to `GULLoggerLevelNotice`.
GULLoggerLevelWarning = 4, // For backwards compatibility, the value matches `ASL_LEVEL_WARNING`.
/// Notice level, corresponding to `OS_LOG_TYPE_DEFAULT`.
GULLoggerLevelNotice = 5, // For backwards compatibility, the value matches `ASL_LEVEL_NOTICE`.
/// Info level, corresponding to `OS_LOG_TYPE_INFO`.
GULLoggerLevelInfo = 6, // For backwards compatibility, the enum value matches `ASL_LEVEL_INFO`.
/// Debug level, corresponding to `OS_LOG_TYPE_DEBUG`.
GULLoggerLevelDebug = 7, // For backwards compatibility, the value matches `ASL_LEVEL_DEBUG`.
/// The minimum (most severe) supported logging level.
GULLoggerLevelMin = GULLoggerLevelError,
/** Maximum log level. */
/// The maximum (least severe) supported logging level.
GULLoggerLevelMax = GULLoggerLevelDebug
} NS_SWIFT_NAME(GoogleLoggerLevel);
NS_ASSUME_NONNULL_END

View File

@@ -25,7 +25,7 @@ NSString *const GULNSDataZlibRemainingBytesKey = @"GULNSDataZlibRemainingBytesKe
@implementation NSData (GULGzip)
+ (NSData *)gul_dataByInflatingGzippedData:(NSData *)data error:(NSError **)error {
+ (nullable NSData *)gul_dataByInflatingGzippedData:(NSData *)data error:(NSError **)error {
const void *bytes = [data bytes];
NSUInteger length = [data length];
if (!bytes || !length) {
@@ -119,7 +119,7 @@ NSString *const GULNSDataZlibRemainingBytesKey = @"GULNSDataZlibRemainingBytesKe
return result;
}
+ (NSData *)gul_dataByGzippingData:(NSData *)data error:(NSError **)error {
+ (nullable NSData *)gul_dataByGzippingData:(NSData *)data error:(NSError **)error {
const void *bytes = [data bytes];
NSUInteger length = [data length];

View File

@@ -14,6 +14,8 @@
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/// This is a copy of Google Toolbox for Mac library to avoid creating an extra framework.
// NOTE: For 64bit, none of these apis handle input sizes >32bits, they will return nil when given
@@ -24,11 +26,11 @@
/// Returns an data as the result of decompressing the payload of |data|.The data to decompress must
/// be a gzipped payloads.
+ (NSData *)gul_dataByInflatingGzippedData:(NSData *)data error:(NSError **)error;
+ (nullable NSData *)gul_dataByInflatingGzippedData:(NSData *)data error:(NSError **)error;
/// Returns an compressed data with the result of gzipping the payload of |data|. Uses the default
/// compression level.
+ (NSData *)gul_dataByGzippingData:(NSData *)data error:(NSError **)error;
+ (nullable NSData *)gul_dataByGzippingData:(NSData *)data error:(NSError **)error;
FOUNDATION_EXPORT NSString *const GULNSDataZlibErrorDomain;
FOUNDATION_EXPORT NSString *const GULNSDataZlibErrorKey; // NSNumber
@@ -47,3 +49,5 @@ typedef NS_ENUM(NSInteger, GULNSDataZlibError) {
};
@end
NS_ASSUME_NONNULL_END

View File

@@ -90,11 +90,11 @@ static NSString *const kGULNetworkLogTag = @"Google/Utilities/Network";
completionHandler:completionHandler];
}
- (NSString *)postURL:(NSURL *)url
payload:(NSData *)payload
queue:(dispatch_queue_t)queue
usingBackgroundSession:(BOOL)usingBackgroundSession
completionHandler:(GULNetworkCompletionHandler)handler {
- (nullable NSString *)postURL:(NSURL *)url
payload:(NSData *)payload
queue:(nullable dispatch_queue_t)queue
usingBackgroundSession:(BOOL)usingBackgroundSession
completionHandler:(GULNetworkCompletionHandler)handler {
return [self postURL:url
headers:nil
payload:payload
@@ -103,12 +103,12 @@ static NSString *const kGULNetworkLogTag = @"Google/Utilities/Network";
completionHandler:handler];
}
- (NSString *)postURL:(NSURL *)url
headers:(NSDictionary *)headers
payload:(NSData *)payload
queue:(dispatch_queue_t)queue
usingBackgroundSession:(BOOL)usingBackgroundSession
completionHandler:(GULNetworkCompletionHandler)handler {
- (nullable NSString *)postURL:(NSURL *)url
headers:(nullable NSDictionary *)headers
payload:(NSData *)payload
queue:(nullable dispatch_queue_t)queue
usingBackgroundSession:(BOOL)usingBackgroundSession
completionHandler:(GULNetworkCompletionHandler)handler {
if (!url.absoluteString.length) {
[self handleErrorWithCode:GULErrorCodeNetworkInvalidURL queue:queue withHandler:handler];
return nil;
@@ -189,11 +189,11 @@ static NSString *const kGULNetworkLogTag = @"Google/Utilities/Network";
return requestID;
}
- (NSString *)getURL:(NSURL *)url
headers:(NSDictionary *)headers
queue:(dispatch_queue_t)queue
usingBackgroundSession:(BOOL)usingBackgroundSession
completionHandler:(GULNetworkCompletionHandler)handler {
- (nullable NSString *)getURL:(NSURL *)url
headers:(nullable NSDictionary *)headers
queue:(nullable dispatch_queue_t)queue
usingBackgroundSession:(BOOL)usingBackgroundSession
completionHandler:(GULNetworkCompletionHandler)handler {
if (!url.absoluteString.length) {
[self handleErrorWithCode:GULErrorCodeNetworkInvalidURL queue:queue withHandler:handler];
return nil;
@@ -279,10 +279,11 @@ static NSString *const kGULNetworkLogTag = @"Google/Utilities/Network";
messageCode:message:context:)] ||
![loggerDelegate respondsToSelector:@selector(GULNetwork_logWithLevel:
messageCode:message:)]) {
GULLogError(kGULLoggerNetwork, NO,
[NSString stringWithFormat:@"I-NET%06ld", (long)kGULNetworkMessageCodeNetwork002],
@"Cannot set the network logger delegate: delegate does not conform to the network "
"logger protocol.");
GULOSLogError(
kGULLogSubsystem, kGULLoggerNetwork, NO,
[NSString stringWithFormat:@"I-NET%06ld", (long)kGULNetworkMessageCodeNetwork002],
@"Cannot set the network logger delegate: delegate does not conform to the network "
"logger protocol.");
return;
}
_loggerDelegate = loggerDelegate;
@@ -329,9 +330,9 @@ static NSString *const kGULNetworkLogTag = @"Google/Utilities/Network";
logLevel == kGULNetworkLogLevelWarning || logLevel == kGULNetworkLogLevelInfo) {
NSString *formattedMessage = GULStringWithLogMessage(message, logLevel, contexts);
NSLog(@"%@", formattedMessage);
GULLogBasic((GULLoggerLevel)logLevel, kGULLoggerNetwork, NO,
[NSString stringWithFormat:@"I-NET%06ld", (long)messageCode], formattedMessage,
NULL);
GULOSLogBasic((GULLoggerLevel)logLevel, kGULLogSubsystem, kGULLoggerNetwork, NO,
[NSString stringWithFormat:@"I-NET%06ld", (long)messageCode], formattedMessage,
NULL);
}
}

View File

@@ -35,14 +35,11 @@
/// Session ID generated randomly with a fixed prefix.
NSString *_sessionID;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunguarded-availability"
/// The session configuration. NSURLSessionConfiguration' is only available on iOS 7.0 or newer.
/// The session configuration.
NSURLSessionConfiguration *_sessionConfig;
/// The current NSURLSession.
NSURLSession *__weak _Nullable _URLSession;
#pragma clang diagnostic pop
/// The path to the directory where all temporary files are stored before uploading.
NSURL *_networkDirectoryURL;
@@ -96,20 +93,19 @@
if (fetcher != nil) {
[fetcher addSystemCompletionHandler:systemCompletionHandler forSession:sessionID];
} else {
GULLogError(kGULLoggerNetwork, NO,
[NSString stringWithFormat:@"I-NET%06ld", (long)kGULNetworkMessageCodeNetwork003],
@"Failed to retrieve background session with ID %@ after app is relaunched.",
sessionID);
GULOSLogError(kGULLogSubsystem, kGULLoggerNetwork, NO,
[NSString stringWithFormat:@"I-NET%06ld", (long)kGULNetworkMessageCodeNetwork003],
@"Failed to retrieve background session with ID %@ after app is relaunched.",
sessionID);
}
}
#pragma mark - External Methods
/// Sends an async POST request using NSURLSession for iOS >= 7.0, and returns an ID of the
/// connection.
/// Sends an async POST request using `NSURLSession`, and returns an ID of the connection.
- (nullable NSString *)sessionIDFromAsyncPOSTRequest:(NSURLRequest *)request
completionHandler:(GULNetworkURLSessionCompletionHandler)handler
API_AVAILABLE(ios(7.0)) {
completionHandler:
(GULNetworkURLSessionCompletionHandler)handler {
// NSURLSessionUploadTask does not work with NSData in the background.
// To avoid this issue, write the data to a temporary file to upload it.
// Make a temporary file with the data subset.
@@ -145,25 +141,25 @@
[self excludeFromBackupForURL:_uploadingFileURL];
_sessionConfig = [self backgroundSessionConfigWithSessionID:_sessionID];
[self populateSessionConfig:_sessionConfig withRequest:request];
session = [NSURLSession sessionWithConfiguration:_sessionConfig
delegate:self
delegateQueue:[NSOperationQueue mainQueue]];
postRequestTask = [session uploadTaskWithRequest:request fromFile:_uploadingFileURL];
} else {
// If we cannot write to file, just send it in the foreground.
_sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
[self populateSessionConfig:_sessionConfig withRequest:request];
session = [NSURLSession sessionWithConfiguration:_sessionConfig
delegate:self
delegateQueue:[NSOperationQueue mainQueue]];
// To avoid a runtime warning in Xcode 15 Beta 4, the given `URLRequest`
// should have a nil `HTTPBody`. To workaround this, the given `URLRequest`
// is copied and the `HTTPBody` data is removed.
NSData *givenRequestHTTPBody = [request.HTTPBody copy];
NSMutableURLRequest *requestWithoutHTTPBody = [request mutableCopy];
requestWithoutHTTPBody.HTTPBody = nil;
}
[self populateSessionConfig:_sessionConfig withRequest:request];
session = [NSURLSession sessionWithConfiguration:_sessionConfig
delegate:self
delegateQueue:[NSOperationQueue mainQueue]];
// To avoid a runtime warning in Xcode 15 Beta 4, the given `URLRequest`
// should have a nil `HTTPBody`. To workaround this, the given `URLRequest`
// is copied and the `HTTPBody` data is removed.
NSData *givenRequestHTTPBody = [request.HTTPBody copy];
NSMutableURLRequest *requestWithoutHTTPBody = [request mutableCopy];
requestWithoutHTTPBody.HTTPBody = nil;
if (didWriteFile) {
postRequestTask = [session uploadTaskWithRequest:requestWithoutHTTPBody
fromFile:_uploadingFileURL];
} else {
postRequestTask = [session uploadTaskWithRequest:requestWithoutHTTPBody
fromData:givenRequestHTTPBody];
}
@@ -192,10 +188,9 @@
return _sessionID;
}
/// Sends an async GET request using NSURLSession for iOS >= 7.0, and returns an ID of the session.
/// Sends an async GET request using `NSURLSession`, and returns an ID of the session.
- (nullable NSString *)sessionIDFromAsyncGETRequest:(NSURLRequest *)request
completionHandler:(GULNetworkURLSessionCompletionHandler)handler
API_AVAILABLE(ios(7.0)) {
completionHandler:(GULNetworkURLSessionCompletionHandler)handler {
if (_backgroundNetworkEnabled) {
_sessionConfig = [self backgroundSessionConfigWithSessionID:_sessionID];
} else {
@@ -260,7 +255,7 @@
/// be called with the downloaded data.
- (void)URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)task
didFinishDownloadingToURL:(NSURL *)url API_AVAILABLE(ios(7.0)) {
didFinishDownloadingToURL:(NSURL *)url {
if (!url.path) {
[_loggerDelegate
GULNetwork_logWithLevel:kGULNetworkLogLevelError
@@ -283,8 +278,7 @@
}
#if TARGET_OS_IOS || TARGET_OS_TV
- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session
API_AVAILABLE(ios(7.0)) {
- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session {
[_loggerDelegate GULNetwork_logWithLevel:kGULNetworkLogLevelDebug
messageCode:kGULNetworkMessageCodeURLSession003
message:@"Background session finished"
@@ -295,7 +289,7 @@
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error API_AVAILABLE(ios(7.0)) {
didCompleteWithError:(NSError *)error {
// Avoid any chance of recursive behavior leading to it being used repeatedly.
GULNetworkURLSessionCompletionHandler handler = _completionHandler;
_completionHandler = nil;
@@ -340,8 +334,7 @@
task:(NSURLSessionTask *)task
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition,
NSURLCredential *credential))completionHandler
API_AVAILABLE(ios(7.0)) {
NSURLCredential *credential))completionHandler {
// The handling is modeled after GTMSessionFetcher.
if ([challenge.protectionSpace.authenticationMethod
isEqualToString:NSURLAuthenticationMethodServerTrust]) {
@@ -394,28 +387,20 @@
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(evaluateBackgroundQueue, ^{
SecTrustResultType trustEval = kSecTrustResultInvalid;
BOOL shouldAllow;
OSStatus trustError;
CFErrorRef errorRef = NULL;
@synchronized([GULNetworkURLSession class]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
trustError = SecTrustEvaluate(serverTrust, &trustEval);
#pragma clang diagnostic pop
shouldAllow = SecTrustEvaluateWithError(serverTrust, &errorRef);
}
if (trustError != errSecSuccess) {
[self->_loggerDelegate GULNetwork_logWithLevel:kGULNetworkLogLevelError
messageCode:kGULNetworkMessageCodeURLSession008
message:@"Cannot evaluate server trust. Error, host"
contexts:@[ @(trustError), self->_request.URL ]];
shouldAllow = NO;
} else {
// Having a trust level "unspecified" by the user is the usual result, described at
// https://developer.apple.com/library/mac/qa/qa1360
shouldAllow =
(trustEval == kSecTrustResultUnspecified || trustEval == kSecTrustResultProceed);
if (errorRef) {
[self->_loggerDelegate
GULNetwork_logWithLevel:kGULNetworkLogLevelError
messageCode:kGULNetworkMessageCodeURLSession008
message:@"Cannot evaluate server trust. Error, host"
contexts:@[ @((int)CFErrorGetCode(errorRef)), self->_request.URL ]];
CFRelease(errorRef);
}
// Call the call back with the permission.
@@ -662,7 +647,7 @@
task:(NSURLSessionTask *)task
willPerformHTTPRedirection:(NSHTTPURLResponse *)response
newRequest:(NSURLRequest *)request
completionHandler:(void (^)(NSURLRequest *))completionHandler API_AVAILABLE(ios(7.0)) {
completionHandler:(void (^)(NSURLRequest *))completionHandler {
NSArray *nonAllowedRedirectionCodes = @[
@(kGULNetworkHTTPStatusCodeFound), @(kGULNetworkHTTPStatusCodeMovedPermanently),
@(kGULNetworkHTTPStatusCodeMovedTemporarily), @(kGULNetworkHTTPStatusCodeMultipleChoices)
@@ -734,7 +719,7 @@
// Always use the request parameters even if the default session configuration is more restrictive.
- (void)populateSessionConfig:(NSURLSessionConfiguration *)sessionConfig
withRequest:(NSURLRequest *)request API_AVAILABLE(ios(7.0)) {
withRequest:(NSURLRequest *)request {
sessionConfig.HTTPAdditionalHeaders = request.allHTTPHeaderFields;
sessionConfig.timeoutIntervalForRequest = request.timeoutInterval;
sessionConfig.timeoutIntervalForResource = request.timeoutInterval;

View File

@@ -16,6 +16,8 @@
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/// A mutable dictionary that provides atomic accessor and mutators.
@interface GULMutableDictionary : NSObject
@@ -44,3 +46,5 @@
- (NSDictionary *)dictionary;
@end
NS_ASSUME_NONNULL_END

View File

@@ -20,6 +20,8 @@
#import "GULNetworkLoggerProtocol.h"
#import "GULNetworkURLSession.h"
NS_ASSUME_NONNULL_BEGIN
/// Delegate protocol for GULNetwork events.
@protocol GULNetworkReachabilityDelegate
@@ -69,29 +71,31 @@
/// Compresses and sends a POST request with the provided data to the URL. The session will be
/// background session if usingBackgroundSession is YES. Otherwise, the POST session is default
/// session. Returns a session ID or nil if an error occurs.
- (NSString *)postURL:(NSURL *)url
payload:(NSData *)payload
queue:(dispatch_queue_t)queue
usingBackgroundSession:(BOOL)usingBackgroundSession
completionHandler:(GULNetworkCompletionHandler)handler;
- (nullable NSString *)postURL:(NSURL *)url
payload:(NSData *)payload
queue:(nullable dispatch_queue_t)queue
usingBackgroundSession:(BOOL)usingBackgroundSession
completionHandler:(GULNetworkCompletionHandler)handler;
/// Compresses and sends a POST request with the provided headers and data to the URL. The session
/// will be background session if usingBackgroundSession is YES. Otherwise, the POST session is
/// default session. Returns a session ID or nil if an error occurs.
- (NSString *)postURL:(NSURL *)url
headers:(NSDictionary *)headers
payload:(NSData *)payload
queue:(dispatch_queue_t)queue
usingBackgroundSession:(BOOL)usingBackgroundSession
completionHandler:(GULNetworkCompletionHandler)handler;
- (nullable NSString *)postURL:(NSURL *)url
headers:(nullable NSDictionary *)headers
payload:(NSData *)payload
queue:(nullable dispatch_queue_t)queue
usingBackgroundSession:(BOOL)usingBackgroundSession
completionHandler:(GULNetworkCompletionHandler)handler;
/// Sends a GET request with the provided data to the URL. The session will be background session
/// if usingBackgroundSession is YES. Otherwise, the GET session is default session. Returns a
/// session ID or nil if an error occurs.
- (NSString *)getURL:(NSURL *)url
headers:(NSDictionary *)headers
queue:(dispatch_queue_t)queue
usingBackgroundSession:(BOOL)usingBackgroundSession
completionHandler:(GULNetworkCompletionHandler)handler;
- (nullable NSString *)getURL:(NSURL *)url
headers:(nullable NSDictionary *)headers
queue:(nullable dispatch_queue_t)queue
usingBackgroundSession:(BOOL)usingBackgroundSession
completionHandler:(GULNetworkCompletionHandler)handler;
@end
NS_ASSUME_NONNULL_END

View File

@@ -16,6 +16,8 @@
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/// Error codes in Firebase Network error domain.
/// Note: these error codes should never change. It would make it harder to decode the errors if
/// we inadvertently altered any of these codes in a future SDK version.
@@ -69,3 +71,5 @@ extern const int kGULNetworkHTTPStatusCodeMovedTemporarily;
extern const int kGULNetworkHTTPStatusCodeNotFound;
extern const int kGULNetworkHTTPStatusCodeCannotAcceptTraffic;
extern const int kGULNetworkHTTPStatusCodeUnavailable;
NS_ASSUME_NONNULL_END

View File

@@ -18,6 +18,8 @@
#import "GULNetworkMessageCode.h"
NS_ASSUME_NONNULL_BEGIN
/// The log levels used by GULNetworkLogger.
typedef NS_ENUM(NSInteger, GULNetworkLogLevel) {
kGULNetworkLogLevelError = 3,
@@ -47,3 +49,5 @@ typedef NS_ENUM(NSInteger, GULNetworkLogLevel) {
message:(NSString *)message;
@end
NS_ASSUME_NONNULL_END

View File

@@ -16,6 +16,8 @@
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
// Make sure these codes do not overlap with any contained in the FIRAMessageCode enum.
typedef NS_ENUM(NSInteger, GULNetworkMessageCode) {
// GULNetwork.m
@@ -45,3 +47,5 @@ typedef NS_ENUM(NSInteger, GULNetworkMessageCode) {
kGULNetworkMessageCodeURLSession018 = 901018, // I-NET901018
kGULNetworkMessageCodeURLSession019 = 901019, // I-NET901019
};
NS_ASSUME_NONNULL_END

View File

@@ -25,7 +25,6 @@
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>1C8F.1</string>
<string>C56D.1</string>
</array>
</dict>

View File

@@ -64,10 +64,10 @@ static NSString *const kGULReachabilityDisconnectedStatus = @"Disconnected";
- (void)setReachabilityApi:(const struct GULReachabilityApi *)reachabilityApi {
#if !TARGET_OS_WATCH
if (reachability_) {
GULLogError(kGULLoggerReachability, NO,
[NSString stringWithFormat:@"I-REA%06ld", (long)kGULReachabilityMessageCode000],
@"Cannot change reachability API while reachability is running. "
@"Call stop first.");
GULOSLogError(kGULLogSubsystem, kGULLoggerReachability, NO,
[NSString stringWithFormat:@"I-REA%06ld", (long)kGULReachabilityMessageCode000],
@"Cannot change reachability API while reachability is running. "
@"Call stop first.");
return;
}
reachabilityApi_ = reachabilityApi;
@@ -89,9 +89,9 @@ static NSString *const kGULReachabilityDisconnectedStatus = @"Disconnected";
- (void)setReachabilityDelegate:(id<GULReachabilityDelegate>)reachabilityDelegate {
if (reachabilityDelegate &&
(![(NSObject *)reachabilityDelegate conformsToProtocol:@protocol(GULReachabilityDelegate)])) {
GULLogError(kGULLoggerReachability, NO,
[NSString stringWithFormat:@"I-NET%06ld", (long)kGULReachabilityMessageCode005],
@"Reachability delegate doesn't conform to Reachability protocol.");
GULOSLogError(kGULLogSubsystem, kGULLoggerReachability, NO,
[NSString stringWithFormat:@"I-NET%06ld", (long)kGULReachabilityMessageCode005],
@"Reachability delegate doesn't conform to Reachability protocol.");
return;
}
reachabilityDelegate_ = reachabilityDelegate;
@@ -102,9 +102,9 @@ static NSString *const kGULReachabilityDisconnectedStatus = @"Disconnected";
self = [super init];
if (!host || !host.length) {
GULLogError(kGULLoggerReachability, NO,
[NSString stringWithFormat:@"I-REA%06ld", (long)kGULReachabilityMessageCode001],
@"Invalid host specified");
GULOSLogError(kGULLogSubsystem, kGULLoggerReachability, NO,
[NSString stringWithFormat:@"I-REA%06ld", (long)kGULReachabilityMessageCode001],
@"Invalid host specified");
return nil;
}
if (self) {
@@ -147,15 +147,15 @@ static NSString *const kGULReachabilityDisconnectedStatus = @"Disconnected";
reachabilityApi_->releaseFn(reachability_);
reachability_ = nil;
GULLogError(kGULLoggerReachability, NO,
[NSString stringWithFormat:@"I-REA%06ld", (long)kGULReachabilityMessageCode002],
@"Failed to start reachability handle");
GULOSLogError(kGULLogSubsystem, kGULLoggerReachability, NO,
[NSString stringWithFormat:@"I-REA%06ld", (long)kGULReachabilityMessageCode002],
@"Failed to start reachability handle");
return NO;
}
}
GULLogDebug(kGULLoggerReachability, NO,
[NSString stringWithFormat:@"I-REA%06ld", (long)kGULReachabilityMessageCode003],
@"Monitoring the network status");
GULOSLogDebug(kGULLogSubsystem, kGULLoggerReachability, NO,
[NSString stringWithFormat:@"I-REA%06ld", (long)kGULReachabilityMessageCode003],
@"Monitoring the network status");
return YES;
#endif
}
@@ -180,7 +180,7 @@ static NSString *const kGULReachabilityDisconnectedStatus = @"Disconnected";
// Reachable flag is set. Check further flags.
if (!(flags & kSCNetworkReachabilityFlagsConnectionRequired)) {
// Connection required flag is not set, so we have connectivity.
#if TARGET_OS_IOS || TARGET_OS_TV || (defined(TARGET_OS_VISION) && TARGET_OS_VISION)
#if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_VISION
status = (flags & kSCNetworkReachabilityFlagsIsWWAN) ? kGULReachabilityViaCellular
: kGULReachabilityViaWifi;
#elif TARGET_OS_OSX
@@ -191,7 +191,7 @@ static NSString *const kGULReachabilityDisconnectedStatus = @"Disconnected";
!(flags & kSCNetworkReachabilityFlagsInterventionRequired)) {
// If the connection on demand or connection on traffic flag is set, and user intervention
// is not required, we have connectivity.
#if TARGET_OS_IOS || TARGET_OS_TV || (defined(TARGET_OS_VISION) && TARGET_OS_VISION)
#if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_VISION
status = (flags & kSCNetworkReachabilityFlagsIsWWAN) ? kGULReachabilityViaCellular
: kGULReachabilityViaWifi;
#elif TARGET_OS_OSX
@@ -214,10 +214,10 @@ static NSString *const kGULReachabilityDisconnectedStatus = @"Disconnected";
: kGULReachabilityConnectedStatus;
}
GULLogDebug(kGULLoggerReachability, NO,
[NSString stringWithFormat:@"I-REA%06ld", (long)kGULReachabilityMessageCode004],
@"Network status has changed. Code:%@, status:%@", @(status),
reachabilityStatusString);
GULOSLogDebug(kGULLogSubsystem, kGULLoggerReachability, NO,
[NSString stringWithFormat:@"I-REA%06ld", (long)kGULReachabilityMessageCode004],
@"Network status has changed. Code:%@, status:%@", @(status),
reachabilityStatusString);
reachabilityStatus_ = status;
[reachabilityDelegate_ reachability:self statusChanged:reachabilityStatus_];
}

View File

@@ -19,6 +19,8 @@
#import <SystemConfiguration/SystemConfiguration.h>
#endif
NS_ASSUME_NONNULL_BEGIN
/// Reachability Status
typedef enum {
kGULReachabilityUnknown, ///< Have not yet checked or been notified whether host is reachable.
@@ -77,3 +79,5 @@ const NSString *GULReachabilityStatusString(GULReachabilityStatus status);
- (void)stop;
@end
NS_ASSUME_NONNULL_END

View File

@@ -18,8 +18,6 @@
NS_ASSUME_NONNULL_BEGIN
static NSTimeInterval const kGULSynchronizeInterval = 1.0;
static NSString *const kGULLogFormat = @"I-GUL%06ld";
static GULLoggerService kGULLogUserDefaultsService = @"[GoogleUtilities/UserDefaults]";
@@ -33,18 +31,11 @@ typedef NS_ENUM(NSInteger, GULUDMessageCode) {
@interface GULUserDefaults ()
/// Equivalent to the suite name for NSUserDefaults.
@property(readonly) CFStringRef appNameRef;
@property(atomic) BOOL isPreferenceFileExcluded;
@property(nonatomic, readonly) NSUserDefaults *userDefaults;
@end
@implementation GULUserDefaults {
// The application name is the same with the suite name of the NSUserDefaults, and it is used for
// preferences.
CFStringRef _appNameRef;
}
@implementation GULUserDefaults
+ (GULUserDefaults *)standardUserDefaults {
static GULUserDefaults *standardUserDefaults;
@@ -65,49 +56,35 @@ typedef NS_ENUM(NSInteger, GULUDMessageCode) {
NSString *name = [suiteName copy];
if (self) {
// `kCFPreferencesCurrentApplication` maps to the same defaults database as
// `[NSUserDefaults standardUserDefaults]`.
_appNameRef =
name.length ? (__bridge_retained CFStringRef)name : kCFPreferencesCurrentApplication;
_userDefaults = name.length ? [[NSUserDefaults alloc] initWithSuiteName:name]
: [NSUserDefaults standardUserDefaults];
}
return self;
}
- (void)dealloc {
// If we're using a custom `_appNameRef` it needs to be released. If it's a constant, it shouldn't
// need to be released since we don't own it.
if (CFStringCompare(_appNameRef, kCFPreferencesCurrentApplication, 0) != kCFCompareEqualTo) {
CFRelease(_appNameRef);
}
[NSObject cancelPreviousPerformRequestsWithTarget:self
selector:@selector(synchronize)
object:nil];
}
- (nullable id)objectForKey:(NSString *)defaultName {
NSString *key = [defaultName copy];
if (![key isKindOfClass:[NSString class]] || !key.length) {
GULLogWarning(@"<GoogleUtilities>", NO,
[NSString stringWithFormat:kGULLogFormat, (long)GULUDMessageCodeInvalidKeyGet],
@"Cannot get object for invalid user default key.");
GULOSLogWarning(kGULLogSubsystem, @"<GoogleUtilities>", NO,
[NSString stringWithFormat:kGULLogFormat, (long)GULUDMessageCodeInvalidKeyGet],
@"Cannot get object for invalid user default key.");
return nil;
}
return (__bridge_transfer id)CFPreferencesCopyAppValue((__bridge CFStringRef)key, _appNameRef);
return [self.userDefaults objectForKey:key];
}
- (void)setObject:(nullable id)value forKey:(NSString *)defaultName {
NSString *key = [defaultName copy];
if (![key isKindOfClass:[NSString class]] || !key.length) {
GULLogWarning(kGULLogUserDefaultsService, NO,
[NSString stringWithFormat:kGULLogFormat, (long)GULUDMessageCodeInvalidKeySet],
@"Cannot set object for invalid user default key.");
GULOSLogWarning(kGULLogSubsystem, kGULLogUserDefaultsService, NO,
[NSString stringWithFormat:kGULLogFormat, (long)GULUDMessageCodeInvalidKeySet],
@"Cannot set object for invalid user default key.");
return;
}
if (!value) {
CFPreferencesSetAppValue((__bridge CFStringRef)key, NULL, _appNameRef);
[self scheduleSynchronize];
[self.userDefaults removeObjectForKey:key];
return;
}
BOOL isAcceptableValue =
@@ -115,16 +92,16 @@ typedef NS_ENUM(NSInteger, GULUDMessageCode) {
[value isKindOfClass:[NSArray class]] || [value isKindOfClass:[NSDictionary class]] ||
[value isKindOfClass:[NSDate class]] || [value isKindOfClass:[NSData class]];
if (!isAcceptableValue) {
GULLogWarning(kGULLogUserDefaultsService, NO,
[NSString stringWithFormat:kGULLogFormat, (long)GULUDMessageCodeInvalidObjectSet],
@"Cannot set invalid object to user defaults. Must be a string, number, array, "
@"dictionary, date, or data. Value: %@",
value);
GULOSLogWarning(
kGULLogSubsystem, kGULLogUserDefaultsService, NO,
[NSString stringWithFormat:kGULLogFormat, (long)GULUDMessageCodeInvalidObjectSet],
@"Cannot set invalid object to user defaults. Must be a string, number, array, "
@"dictionary, date, or data. Value: %@",
value);
return;
}
CFPreferencesSetAppValue((__bridge CFStringRef)key, (__bridge CFStringRef)value, _appNameRef);
[self scheduleSynchronize];
[self.userDefaults setObject:value forKey:key];
}
- (void)removeObjectForKey:(NSString *)key {
@@ -183,31 +160,6 @@ typedef NS_ENUM(NSInteger, GULUDMessageCode) {
[self setObject:@(boolValue) forKey:defaultName];
}
#pragma mark - Save data
- (void)synchronize {
if (!CFPreferencesAppSynchronize(_appNameRef)) {
GULLogError(kGULLogUserDefaultsService, NO,
[NSString stringWithFormat:kGULLogFormat, (long)GULUDMessageCodeSynchronizeFailed],
@"Cannot synchronize user defaults to disk");
}
}
#pragma mark - Private methods
- (void)scheduleSynchronize {
// Synchronize data using a timer so that multiple set... calls can be coalesced under one
// synchronize.
[NSObject cancelPreviousPerformRequestsWithTarget:self
selector:@selector(synchronize)
object:nil];
// This method may be called on multiple queues (due to set... methods can be called on any queue)
// synchronize can be scheduled on different queues, so make sure that it does not crash. If this
// instance goes away, self will be released also, no one will retain it and the schedule won't be
// called.
[self performSelector:@selector(synchronize) withObject:nil afterDelay:kGULSynchronizeInterval];
}
@end
NS_ASSUME_NONNULL_END

View File

@@ -100,11 +100,6 @@ NS_ASSUME_NONNULL_BEGIN
/// Equivalent to -[... setObject:nil forKey:defaultName]
- (void)removeObjectForKey:(NSString *)defaultName;
#pragma mark - Save data
/// Blocks the calling thread until all in-progress set operations have completed.
- (void)synchronize;
@end
NS_ASSUME_NONNULL_END

View File

@@ -38,7 +38,7 @@ After the CI is green:
<summary>Push to <b>SpecsStaging</b></summary>
```console
pod repo push --skip-tests staging GoogleUtilities.podspec
pod repo push --skip-tests --use-json staging GoogleUtilities.podspec
```
If the command fails with `Unable to find the 'staging' repo.`, add the staging repo with:
@@ -51,7 +51,7 @@ After the CI is green:
<summary>Push to <b>SpecsDev</b></summary>
```console
pod repo push --skip-tests dev GoogleUtilities.podspec
pod repo push --skip-tests --use-json dev GoogleUtilities.podspec
```
If the command fails with `Unable to find the 'dev' repo.`, add the dev repo with:
@@ -89,7 +89,7 @@ The release process is as follows:
It's recommended to point to the `GoogleUtilities.podspec` in `staging` to make sure the correct spec is being published.
```console
pod trunk push ~/.cocoapods/repos/staging/GoogleUtilities/{version}/GoogleUtilities.podspec
pod trunk push ~/.cocoapods/repos/staging/GoogleUtilities/{version}/GoogleUtilities.podspec.json
```
*Note: In some cases, it may be acceptable to `pod trunk push` with the `--skip-tests` flag. Please double check with
the maintainers before doing so.*
@@ -160,17 +160,7 @@ Alternatively disable signing in each target:
### Code Formatting
To ensure that the code is formatted consistently, run the script
[./scripts/check.sh](https://github.com/firebase/firebase-ios-sdk/blob/main/scripts/check.sh)
before creating a PR.
GitHub Actions will verify that any code changes are done in a style compliant
way. Install `clang-format` and `mint`:
```console
brew install clang-format@17
brew install mint
```
See [firebase-ios-sdk's code formatting docs][firebase-ios-sdk-formatting].
### Running Unit Tests
@@ -187,3 +177,4 @@ The contents of this repository is licensed under the
[gh-actions]: https://github.com/firebase/firebase-ios-sdk/actions
[gh-google-utilities-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/google-utilities/badge.svg
[firebase-ios-sdk-formatting]: https://github.com/firebase/firebase-ios-sdk?tab=readme-ov-file#code-formatting