AutorÃa | Ultima modificación | Ver Log |
/*
* 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 "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORPlatform.h"
#import <sys/sysctl.h>
#import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORAssert.h"
#import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORReachability.h"
#import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORConsoleLogger.h"
#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORRegistrar_Private.h"
#ifdef GDTCOR_VERSION
#define STR(x) STR_EXPAND(x)
#define STR_EXPAND(x) #x
NSString *const kGDTCORVersion = @STR(GDTCOR_VERSION);
#else
NSString *const kGDTCORVersion = @"Unknown";
#endif // GDTCOR_VERSION
const GDTCORBackgroundIdentifier GDTCORBackgroundIdentifierInvalid = 0;
NSString *const kGDTCORApplicationDidEnterBackgroundNotification =
@"GDTCORApplicationDidEnterBackgroundNotification";
NSString *const kGDTCORApplicationWillEnterForegroundNotification =
@"GDTCORApplicationWillEnterForegroundNotification";
NSString *const kGDTCORApplicationWillTerminateNotification =
@"GDTCORApplicationWillTerminateNotification";
NSURL *GDTCORRootDirectory(void) {
static NSURL *GDTPath;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSString *cachePath =
NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)[0];
GDTPath =
[NSURL fileURLWithPath:[NSString stringWithFormat:@"%@/google-sdks-events", cachePath]];
GDTCORLogDebug(@"GDT's state will be saved to: %@", GDTPath);
});
NSError *error;
[[NSFileManager defaultManager] createDirectoryAtPath:GDTPath.path
withIntermediateDirectories:YES
attributes:nil
error:&error];
GDTCORAssert(error == nil, @"There was an error creating GDT's path");
return GDTPath;
}
BOOL GDTCORReachabilityFlagsReachable(GDTCORNetworkReachabilityFlags flags) {
#if !TARGET_OS_WATCH
BOOL reachable =
(flags & kSCNetworkReachabilityFlagsReachable) == kSCNetworkReachabilityFlagsReachable;
BOOL connectionRequired = (flags & kSCNetworkReachabilityFlagsConnectionRequired) ==
kSCNetworkReachabilityFlagsConnectionRequired;
return reachable && !connectionRequired;
#else
return (flags & kGDTCORNetworkReachabilityFlagsReachable) ==
kGDTCORNetworkReachabilityFlagsReachable;
#endif
}
BOOL GDTCORReachabilityFlagsContainWWAN(GDTCORNetworkReachabilityFlags flags) {
#if TARGET_OS_IOS
return (flags & kSCNetworkReachabilityFlagsIsWWAN) == kSCNetworkReachabilityFlagsIsWWAN;
#else
// Assume network connection not WWAN on macOS, tvOS, watchOS.
return NO;
#endif // TARGET_OS_IOS
}
GDTCORNetworkType GDTCORNetworkTypeMessage() {
#if !TARGET_OS_WATCH
SCNetworkReachabilityFlags reachabilityFlags = [GDTCORReachability currentFlags];
if ((reachabilityFlags & kSCNetworkReachabilityFlagsReachable) ==
kSCNetworkReachabilityFlagsReachable) {
if (GDTCORReachabilityFlagsContainWWAN(reachabilityFlags)) {
return GDTCORNetworkTypeMobile;
} else {
return GDTCORNetworkTypeWIFI;
}
}
#endif
return GDTCORNetworkTypeUNKNOWN;
}
GDTCORNetworkMobileSubtype GDTCORNetworkMobileSubTypeMessage() {
#if TARGET_OS_IOS
static NSDictionary<NSString *, NSNumber *> *CTRadioAccessTechnologyToNetworkSubTypeMessage;
static CTTelephonyNetworkInfo *networkInfo;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
CTRadioAccessTechnologyToNetworkSubTypeMessage = @{
CTRadioAccessTechnologyGPRS : @(GDTCORNetworkMobileSubtypeGPRS),
CTRadioAccessTechnologyEdge : @(GDTCORNetworkMobileSubtypeEdge),
CTRadioAccessTechnologyWCDMA : @(GDTCORNetworkMobileSubtypeWCDMA),
CTRadioAccessTechnologyHSDPA : @(GDTCORNetworkMobileSubtypeHSDPA),
CTRadioAccessTechnologyHSUPA : @(GDTCORNetworkMobileSubtypeHSUPA),
CTRadioAccessTechnologyCDMA1x : @(GDTCORNetworkMobileSubtypeCDMA1x),
CTRadioAccessTechnologyCDMAEVDORev0 : @(GDTCORNetworkMobileSubtypeCDMAEVDORev0),
CTRadioAccessTechnologyCDMAEVDORevA : @(GDTCORNetworkMobileSubtypeCDMAEVDORevA),
CTRadioAccessTechnologyCDMAEVDORevB : @(GDTCORNetworkMobileSubtypeCDMAEVDORevB),
CTRadioAccessTechnologyeHRPD : @(GDTCORNetworkMobileSubtypeHRPD),
CTRadioAccessTechnologyLTE : @(GDTCORNetworkMobileSubtypeLTE),
};
networkInfo = [[CTTelephonyNetworkInfo alloc] init];
});
NSString *networkCurrentRadioAccessTechnology;
#if TARGET_OS_MACCATALYST
NSDictionary<NSString *, NSString *> *networkCurrentRadioAccessTechnologyDict =
networkInfo.serviceCurrentRadioAccessTechnology;
if (networkCurrentRadioAccessTechnologyDict.count) {
networkCurrentRadioAccessTechnology = networkCurrentRadioAccessTechnologyDict.allValues[0];
}
#else // TARGET_OS_MACCATALYST
if (@available(iOS 12.0, *)) {
NSDictionary<NSString *, NSString *> *networkCurrentRadioAccessTechnologyDict =
networkInfo.serviceCurrentRadioAccessTechnology;
if (networkCurrentRadioAccessTechnologyDict.count) {
// In iOS 12, multiple radio technologies can be captured. We prefer not particular radio
// tech to another, so we'll just return the first value in the dictionary.
networkCurrentRadioAccessTechnology = networkCurrentRadioAccessTechnologyDict.allValues[0];
}
} else {
#if TARGET_OS_IOS && __IPHONE_OS_VERSION_MIN_REQUIRED < 120000
networkCurrentRadioAccessTechnology = networkInfo.currentRadioAccessTechnology;
#endif // TARGET_OS_IOS && __IPHONE_OS_VERSION_MIN_REQUIRED < 120000
}
#endif // TARGET_OS_MACCATALYST
if (networkCurrentRadioAccessTechnology) {
NSNumber *networkMobileSubtype =
CTRadioAccessTechnologyToNetworkSubTypeMessage[networkCurrentRadioAccessTechnology];
return networkMobileSubtype.intValue;
} else {
return GDTCORNetworkMobileSubtypeUNKNOWN;
}
#else // TARGET_OS_IOS
return GDTCORNetworkMobileSubtypeUNKNOWN;
#endif // TARGET_OS_IOS
}
NSString *_Nonnull GDTCORDeviceModel() {
static NSString *deviceModel = @"Unknown";
#if TARGET_OS_IOS || TARGET_OS_TV
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
size_t size;
char *keyToExtract = "hw.machine";
sysctlbyname(keyToExtract, NULL, &size, NULL, 0);
if (size > 0) {
char *machine = calloc(1, size);
sysctlbyname(keyToExtract, machine, &size, NULL, 0);
deviceModel = [NSString stringWithCString:machine encoding:NSUTF8StringEncoding];
free(machine);
} else {
deviceModel = [UIDevice currentDevice].model;
}
});
#endif
return deviceModel;
}
NSData *_Nullable GDTCOREncodeArchive(id<NSSecureCoding> obj,
NSString *filePath,
NSError *_Nullable *error) {
BOOL result = NO;
if (filePath.length > 0) {
result = [[NSFileManager defaultManager]
createDirectoryAtPath:[filePath stringByDeletingLastPathComponent]
withIntermediateDirectories:YES
attributes:nil
error:error];
if (result == NO || *error) {
GDTCORLogDebug(@"Attempt to create directory failed: path:%@ error:%@", filePath, *error);
return nil;
}
}
NSData *resultData;
if (@available(macOS 10.13, iOS 11.0, tvOS 11.0, watchOS 4, *)) {
resultData = [NSKeyedArchiver archivedDataWithRootObject:obj
requiringSecureCoding:YES
error:error];
if (resultData == nil || (error != NULL && *error != nil)) {
GDTCORLogDebug(@"Encoding an object failed: %@", *error);
return nil;
}
if (filePath.length > 0) {
result = [resultData writeToFile:filePath options:NSDataWritingAtomic error:error];
if (result == NO || *error) {
GDTCORLogDebug(@"Attempt to write archive failed: path:%@ error:%@", filePath, *error);
} else {
GDTCORLogDebug(@"Writing archive succeeded: %@", filePath);
}
}
} else {
@try {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
resultData = [NSKeyedArchiver archivedDataWithRootObject:obj];
#pragma clang diagnostic pop
if (filePath.length > 0) {
result = [resultData writeToFile:filePath options:NSDataWritingAtomic error:error];
if (result == NO || *error) {
GDTCORLogDebug(@"Attempt to write archive failed: URL:%@ error:%@", filePath, *error);
} else {
GDTCORLogDebug(@"Writing archive succeeded: %@", filePath);
}
}
} @catch (NSException *exception) {
NSString *errorString =
[NSString stringWithFormat:@"An exception was thrown during encoding: %@", exception];
*error = [NSError errorWithDomain:NSCocoaErrorDomain
code:-1
userInfo:@{NSLocalizedFailureReasonErrorKey : errorString}];
}
if (filePath.length > 0) {
GDTCORLogDebug(@"Attempt to write archive. successful:%@ URL:%@ error:%@",
result ? @"YES" : @"NO", filePath, *error);
}
}
return resultData;
}
id<NSSecureCoding> _Nullable GDTCORDecodeArchive(Class archiveClass,
NSString *_Nullable archivePath,
NSData *_Nullable archiveData,
NSError *_Nullable *error) {
id<NSSecureCoding> unarchivedObject = nil;
if (@available(macOS 10.13, iOS 11.0, tvOS 11.0, watchOS 4, *)) {
NSData *data = archiveData ? archiveData : [NSData dataWithContentsOfFile:archivePath];
if (data) {
unarchivedObject = [NSKeyedUnarchiver unarchivedObjectOfClass:archiveClass
fromData:data
error:error];
}
} else {
@try {
NSData *archivedData =
archiveData ? archiveData : [NSData dataWithContentsOfFile:archivePath];
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
unarchivedObject = [NSKeyedUnarchiver unarchiveObjectWithData:archivedData];
#pragma clang diagnostic pop
} @catch (NSException *exception) {
NSString *errorString =
[NSString stringWithFormat:@"An exception was thrown during encoding: %@", exception];
*error = [NSError errorWithDomain:NSCocoaErrorDomain
code:-1
userInfo:@{NSLocalizedFailureReasonErrorKey : errorString}];
}
}
return unarchivedObject;
}
BOOL GDTCORWriteDataToFile(NSData *data, NSString *filePath, NSError *_Nullable *outError) {
BOOL result = NO;
if (filePath.length > 0) {
result = [[NSFileManager defaultManager]
createDirectoryAtPath:[filePath stringByDeletingLastPathComponent]
withIntermediateDirectories:YES
attributes:nil
error:outError];
if (result == NO || *outError) {
GDTCORLogDebug(@"Attempt to create directory failed: path:%@ error:%@", filePath, *outError);
return result;
}
}
if (filePath.length > 0) {
result = [data writeToFile:filePath options:NSDataWritingAtomic error:outError];
if (result == NO || *outError) {
GDTCORLogDebug(@"Attempt to write archive failed: path:%@ error:%@", filePath, *outError);
} else {
GDTCORLogDebug(@"Writing archive succeeded: %@", filePath);
}
}
return result;
}
@interface GDTCORApplication ()
/**
Private flag to match the existing `readonly` public flag. This will be accurate for all platforms,
since we handle each platform's lifecycle notifications separately.
*/
@property(atomic, readwrite) BOOL isRunningInBackground;
@end
@implementation GDTCORApplication
#if TARGET_OS_WATCH
/** A dispatch queue on which all task semaphores will populate and remove from
* gBackgroundIdentifierToSemaphoreMap.
*/
static dispatch_queue_t gSemaphoreQueue;
/** For mapping backgroundIdentifier to task semaphore. */
static NSMutableDictionary<NSNumber *, dispatch_semaphore_t> *gBackgroundIdentifierToSemaphoreMap;
#endif
+ (void)load {
GDTCORLogDebug(
@"%@", @"GDT is initializing. Please note that if you quit the app via the "
"debugger and not through a lifecycle event, event data will remain on disk but "
"storage won't have a reference to them since the singleton wasn't saved to disk.");
#if TARGET_OS_IOS || TARGET_OS_TV
// If this asserts, please file a bug at https://github.com/firebase/firebase-ios-sdk/issues.
GDTCORFatalAssert(
GDTCORBackgroundIdentifierInvalid == UIBackgroundTaskInvalid,
@"GDTCORBackgroundIdentifierInvalid and UIBackgroundTaskInvalid should be the same.");
#endif
[self sharedApplication];
}
+ (void)initialize {
#if TARGET_OS_WATCH
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
gSemaphoreQueue = dispatch_queue_create("com.google.GDTCORApplication", DISPATCH_QUEUE_SERIAL);
GDTCORLogDebug(
@"%@",
@"GDTCORApplication is initializing on watchOS, gSemaphoreQueue has been initialized.");
gBackgroundIdentifierToSemaphoreMap = [[NSMutableDictionary alloc] init];
GDTCORLogDebug(@"%@", @"GDTCORApplication is initializing on watchOS, "
@"gBackgroundIdentifierToSemaphoreMap has been initialized.");
});
#endif
}
+ (nullable GDTCORApplication *)sharedApplication {
static GDTCORApplication *application;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
application = [[GDTCORApplication alloc] init];
});
return application;
}
- (instancetype)init {
self = [super init];
if (self) {
// This class will be instantiated in the foreground.
_isRunningInBackground = NO;
#if TARGET_OS_IOS || TARGET_OS_TV
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
[notificationCenter addObserver:self
selector:@selector(iOSApplicationDidEnterBackground:)
name:UIApplicationDidEnterBackgroundNotification
object:nil];
[notificationCenter addObserver:self
selector:@selector(iOSApplicationWillEnterForeground:)
name:UIApplicationWillEnterForegroundNotification
object:nil];
NSString *name = UIApplicationWillTerminateNotification;
[notificationCenter addObserver:self
selector:@selector(iOSApplicationWillTerminate:)
name:name
object:nil];
#if defined(__IPHONE_13_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000
if (@available(iOS 13, tvOS 13.0, *)) {
[notificationCenter addObserver:self
selector:@selector(iOSApplicationWillEnterForeground:)
name:UISceneWillEnterForegroundNotification
object:nil];
[notificationCenter addObserver:self
selector:@selector(iOSApplicationDidEnterBackground:)
name:UISceneWillDeactivateNotification
object:nil];
}
#endif // defined(__IPHONE_13_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000
#elif TARGET_OS_OSX
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
[notificationCenter addObserver:self
selector:@selector(macOSApplicationWillTerminate:)
name:NSApplicationWillTerminateNotification
object:nil];
#elif TARGET_OS_WATCH
// TODO: Notification on watchOS platform is currently posted by strings which are frangible.
// TODO: Needs improvements here.
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
[notificationCenter addObserver:self
selector:@selector(iOSApplicationDidEnterBackground:)
name:@"UIApplicationDidEnterBackgroundNotification"
object:nil];
[notificationCenter addObserver:self
selector:@selector(iOSApplicationWillEnterForeground:)
name:@"UIApplicationWillEnterForegroundNotification"
object:nil];
// Adds observers for app extension on watchOS platform
[notificationCenter addObserver:self
selector:@selector(iOSApplicationDidEnterBackground:)
name:NSExtensionHostDidEnterBackgroundNotification
object:nil];
[notificationCenter addObserver:self
selector:@selector(iOSApplicationWillEnterForeground:)
name:NSExtensionHostWillEnterForegroundNotification
object:nil];
#endif
}
return self;
}
#if TARGET_OS_WATCH
/** Generates and maps a unique background identifier to the given semaphore.
*
* @param semaphore The semaphore to map.
* @return A unique GDTCORBackgroundIdentifier mapped to the given semaphore.
*/
+ (GDTCORBackgroundIdentifier)createAndMapBackgroundIdentifierToSemaphore:
(dispatch_semaphore_t)semaphore {
__block GDTCORBackgroundIdentifier bgID = GDTCORBackgroundIdentifierInvalid;
dispatch_queue_t queue = gSemaphoreQueue;
NSMutableDictionary<NSNumber *, dispatch_semaphore_t> *map = gBackgroundIdentifierToSemaphoreMap;
if (queue && map) {
dispatch_sync(queue, ^{
bgID = arc4random();
NSNumber *bgIDNumber = @(bgID);
while (bgID == GDTCORBackgroundIdentifierInvalid || map[bgIDNumber]) {
bgID = arc4random();
bgIDNumber = @(bgID);
}
map[bgIDNumber] = semaphore;
});
}
return bgID;
}
/** Returns the semaphore mapped to given bgID and removes the value from the map.
*
* @param bgID The unique NSUInteger as GDTCORBackgroundIdentifier.
* @return The semaphore mapped by given bgID.
*/
+ (dispatch_semaphore_t)semaphoreForBackgroundIdentifier:(GDTCORBackgroundIdentifier)bgID {
__block dispatch_semaphore_t semaphore;
dispatch_queue_t queue = gSemaphoreQueue;
NSMutableDictionary<NSNumber *, dispatch_semaphore_t> *map = gBackgroundIdentifierToSemaphoreMap;
NSNumber *bgIDNumber = @(bgID);
if (queue && map) {
dispatch_sync(queue, ^{
semaphore = map[bgIDNumber];
[map removeObjectForKey:bgIDNumber];
});
}
return semaphore;
}
#endif
- (GDTCORBackgroundIdentifier)beginBackgroundTaskWithName:(NSString *)name
expirationHandler:(void (^)(void))handler {
__block GDTCORBackgroundIdentifier bgID = GDTCORBackgroundIdentifierInvalid;
#if !TARGET_OS_WATCH
bgID = [[self sharedApplicationForBackgroundTask] beginBackgroundTaskWithName:name
expirationHandler:handler];
#if !NDEBUG
if (bgID != GDTCORBackgroundIdentifierInvalid) {
GDTCORLogDebug(@"Creating background task with name:%@ bgID:%ld", name, (long)bgID);
}
#endif // !NDEBUG
#elif TARGET_OS_WATCH
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
bgID = [GDTCORApplication createAndMapBackgroundIdentifierToSemaphore:semaphore];
if (bgID != GDTCORBackgroundIdentifierInvalid) {
GDTCORLogDebug(@"Creating activity with name:%@ bgID:%ld on watchOS.", name, (long)bgID);
}
[[self sharedNSProcessInfoForBackgroundTask]
performExpiringActivityWithReason:name
usingBlock:^(BOOL expired) {
if (expired) {
if (handler) {
handler();
}
dispatch_semaphore_signal(semaphore);
GDTCORLogDebug(
@"Activity with name:%@ bgID:%ld on watchOS is expiring.",
name, (long)bgID);
} else {
dispatch_semaphore_wait(
semaphore,
dispatch_time(DISPATCH_TIME_NOW, 30 * NSEC_PER_SEC));
}
}];
#endif
return bgID;
}
- (void)endBackgroundTask:(GDTCORBackgroundIdentifier)bgID {
#if !TARGET_OS_WATCH
if (bgID != GDTCORBackgroundIdentifierInvalid) {
GDTCORLogDebug(@"Ending background task with ID:%ld was successful", (long)bgID);
[[self sharedApplicationForBackgroundTask] endBackgroundTask:bgID];
return;
}
#elif TARGET_OS_WATCH
if (bgID != GDTCORBackgroundIdentifierInvalid) {
dispatch_semaphore_t semaphore = [GDTCORApplication semaphoreForBackgroundIdentifier:bgID];
GDTCORLogDebug(@"Ending activity with bgID:%ld on watchOS.", (long)bgID);
if (semaphore) {
dispatch_semaphore_signal(semaphore);
GDTCORLogDebug(@"Signaling semaphore with bgID:%ld on watchOS.", (long)bgID);
} else {
GDTCORLogDebug(@"Semaphore with bgID:%ld is nil on watchOS.", (long)bgID);
}
}
#endif // !TARGET_OS_WATCH
}
#pragma mark - App environment helpers
- (BOOL)isAppExtension {
BOOL appExtension = [[[NSBundle mainBundle] bundlePath] hasSuffix:@".appex"];
return appExtension;
}
/** Returns a UIApplication or NSProcessInfo instance if on the appropriate platform.
*
* @return The shared UIApplication or NSProcessInfo if on the appropriate platform.
*/
#if TARGET_OS_IOS || TARGET_OS_TV
- (nullable UIApplication *)sharedApplicationForBackgroundTask {
#elif TARGET_OS_WATCH
- (nullable NSProcessInfo *)sharedNSProcessInfoForBackgroundTask {
#else
- (nullable id)sharedApplicationForBackgroundTask {
#endif
id sharedInstance = nil;
#if TARGET_OS_IOS || TARGET_OS_TV
if (![self isAppExtension]) {
Class uiApplicationClass = NSClassFromString(@"UIApplication");
if (uiApplicationClass &&
[uiApplicationClass respondsToSelector:(NSSelectorFromString(@"sharedApplication"))]) {
sharedInstance = [uiApplicationClass sharedApplication];
}
}
#elif TARGET_OS_WATCH
sharedInstance = [NSProcessInfo processInfo];
#endif
return sharedInstance;
}
#pragma mark - UIApplicationDelegate and WKExtensionDelegate
#if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_WATCH
- (void)iOSApplicationDidEnterBackground:(NSNotification *)notif {
_isRunningInBackground = YES;
NSNotificationCenter *notifCenter = [NSNotificationCenter defaultCenter];
GDTCORLogDebug(@"%@", @"GDTCORPlatform is sending a notif that the app is backgrounding.");
[notifCenter postNotificationName:kGDTCORApplicationDidEnterBackgroundNotification object:nil];
}
- (void)iOSApplicationWillEnterForeground:(NSNotification *)notif {
_isRunningInBackground = NO;
NSNotificationCenter *notifCenter = [NSNotificationCenter defaultCenter];
GDTCORLogDebug(@"%@", @"GDTCORPlatform is sending a notif that the app is foregrounding.");
[notifCenter postNotificationName:kGDTCORApplicationWillEnterForegroundNotification object:nil];
}
#endif // TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_WATCH
#pragma mark - UIApplicationDelegate
#if TARGET_OS_IOS || TARGET_OS_TV
- (void)iOSApplicationWillTerminate:(NSNotification *)notif {
NSNotificationCenter *notifCenter = [NSNotificationCenter defaultCenter];
GDTCORLogDebug(@"%@", @"GDTCORPlatform is sending a notif that the app is terminating.");
[notifCenter postNotificationName:kGDTCORApplicationWillTerminateNotification object:nil];
}
#endif // TARGET_OS_IOS || TARGET_OS_TV
#pragma mark - NSApplicationDelegate
#if TARGET_OS_OSX
- (void)macOSApplicationWillTerminate:(NSNotification *)notif {
NSNotificationCenter *notifCenter = [NSNotificationCenter defaultCenter];
GDTCORLogDebug(@"%@", @"GDTCORPlatform is sending a notif that the app is terminating.");
[notifCenter postNotificationName:kGDTCORApplicationWillTerminateNotification object:nil];
}
#endif // TARGET_OS_OSX
@end