12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034 |
- // Copyright 2018 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 "TargetConditionals.h"
-
- #import <GoogleUtilities/GULAppDelegateSwizzler.h>
- #import <GoogleUtilities/GULAppEnvironmentUtil.h>
- #import <GoogleUtilities/GULLogger.h>
- #import <GoogleUtilities/GULMutableDictionary.h>
- #import "../Common/GULLoggerCodes.h"
- #import "Internal/GULAppDelegateSwizzler_Private.h"
-
- #import <objc/runtime.h>
-
- // Implementations need to be typed before calling the implementation directly to cast the
- // arguments and the return types correctly. Otherwise, it will crash the app.
- typedef BOOL (*GULRealOpenURLSourceApplicationAnnotationIMP)(
- id, SEL, GULApplication *, NSURL *, NSString *, id);
-
- typedef BOOL (*GULRealOpenURLOptionsIMP)(
- id, SEL, GULApplication *, NSURL *, NSDictionary<NSString *, id> *);
-
- #pragma clang diagnostic push
- #pragma clang diagnostic ignored "-Wstrict-prototypes"
- typedef void (*GULRealHandleEventsForBackgroundURLSessionIMP)(
- id, SEL, GULApplication *, NSString *, void (^)());
- #pragma clang diagnostic pop
-
- // This is needed to for the library to be warning free on iOS versions < 8.
- #pragma clang diagnostic push
- #pragma clang diagnostic ignored "-Wunguarded-availability"
- typedef BOOL (*GULRealContinueUserActivityIMP)(
- id, SEL, GULApplication *, NSUserActivity *, void (^)(NSArray *restorableObjects));
- #pragma clang diagnostic pop
-
- typedef void (*GULRealDidRegisterForRemoteNotificationsIMP)(id, SEL, GULApplication *, NSData *);
-
- typedef void (*GULRealDidFailToRegisterForRemoteNotificationsIMP)(id,
- SEL,
- GULApplication *,
- NSError *);
-
- typedef void (*GULRealDidReceiveRemoteNotificationIMP)(id, SEL, GULApplication *, NSDictionary *);
-
- #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000
- // This is needed to for the library to be warning free on iOS versions < 7.
- #pragma clang diagnostic push
- #pragma clang diagnostic ignored "-Wunguarded-availability"
- typedef void (*GULRealDidReceiveRemoteNotificationWithCompletionIMP)(
- id, SEL, GULApplication *, NSDictionary *, void (^)(UIBackgroundFetchResult));
- #pragma clang diagnostic pop
- #endif // __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000
-
- typedef void (^GULAppDelegateInterceptorCallback)(id<GULApplicationDelegate>);
-
- // The strings below are the keys for associated objects.
- static char const *const kGULRealIMPBySelectorKey = "GUL_realIMPBySelector";
- static char const *const kGULRealClassKey = "GUL_realClass";
-
- static NSString *const kGULAppDelegateKeyPath = @"delegate";
-
- static GULLoggerService kGULLoggerSwizzler = @"[GoogleUtilities/AppDelegateSwizzler]";
-
- // Since Firebase SDKs also use this for app delegate proxying, in order to not be a breaking change
- // we disable App Delegate proxying when either of these two flags are set to NO.
-
- /** Plist key that allows Firebase developers to disable App Delegate Proxying. */
- static NSString *const kGULFirebaseAppDelegateProxyEnabledPlistKey =
- @"FirebaseAppDelegateProxyEnabled";
-
- /** Plist key that allows developers not using Firebase to disable App Delegate Proxying. */
- static NSString *const kGULGoogleUtilitiesAppDelegateProxyEnabledPlistKey =
- @"GoogleUtilitiesAppDelegateProxyEnabled";
-
- /** The prefix of the App Delegate. */
- static NSString *const kGULAppDelegatePrefix = @"GUL_";
-
- /** The original instance of App Delegate. */
- static id<GULApplicationDelegate> gOriginalAppDelegate;
-
- /** The original App Delegate class */
- static Class gOriginalAppDelegateClass;
-
- /** The subclass of the original App Delegate. */
- static Class gAppDelegateSubclass;
-
- /** Remote notification methods selectors
- *
- * We have to opt out of referencing APNS related App Delegate methods directly to prevent
- * an Apple review warning email about missing Push Notification Entitlement
- * (like here: https://github.com/firebase/firebase-ios-sdk/issues/2807). From our experience, the
- * warning is triggered when any of the symbols is present in the application sent to review, even
- * if the code is never executed. Because GULAppDelegateSwizzler may be used by applications that
- * are not using APNS we have to refer to the methods indirectly using selector constructed from
- * string.
- *
- * NOTE: None of the methods is proxied unless it is explicitly requested by calling the method
- * +[GULAppDelegateSwizzler proxyOriginalDelegateIncludingAPNSMethods]
- */
- 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:";
-
- /**
- * This class is necessary to store the delegates in an NSArray without retaining them.
- * [NSValue valueWithNonRetainedObject] also provides this functionality, but does not provide a
- * zeroing pointer. This will cause EXC_BAD_ACCESS when trying to access the object after it is
- * dealloced. Instead, this container stores a weak, zeroing reference to the object, which
- * automatically is set to nil by the runtime when the object is dealloced.
- */
- @interface GULZeroingWeakContainer : NSObject
-
- /** Stores a weak object. */
- @property(nonatomic, weak) id object;
-
- @end
-
- @implementation GULZeroingWeakContainer
- @end
-
- @interface GULAppDelegateObserver : NSObject
- @end
-
- @implementation GULAppDelegateObserver {
- BOOL _isObserving;
- }
-
- + (GULAppDelegateObserver *)sharedInstance {
- static GULAppDelegateObserver *instance;
- static dispatch_once_t once;
- dispatch_once(&once, ^{
- instance = [[GULAppDelegateObserver alloc] init];
- });
- return instance;
- }
-
- - (void)observeUIApplication {
- if (_isObserving) {
- return;
- }
- [[GULAppDelegateSwizzler sharedApplication]
- addObserver:self
- forKeyPath:kGULAppDelegateKeyPath
- options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
- context:nil];
- _isObserving = YES;
- }
-
- - (void)observeValueForKeyPath:(NSString *)keyPath
- ofObject:(id)object
- change:(NSDictionary *)change
- context:(void *)context {
- if ([keyPath isEqual:kGULAppDelegateKeyPath]) {
- id newValue = change[NSKeyValueChangeNewKey];
- id oldValue = change[NSKeyValueChangeOldKey];
- if ([newValue isEqual:oldValue]) {
- return;
- }
- // Free the stored app delegate instance because it has been changed to a different instance to
- // avoid keeping it alive forever.
- if ([oldValue isEqual:gOriginalAppDelegate]) {
- gOriginalAppDelegate = nil;
- // Remove the observer. Parse it to NSObject to avoid warning.
- [[GULAppDelegateSwizzler sharedApplication] removeObserver:self
- forKeyPath:kGULAppDelegateKeyPath];
- _isObserving = NO;
- }
- }
- }
-
- @end
-
- @implementation GULAppDelegateSwizzler
-
- static dispatch_once_t sProxyAppDelegateOnceToken;
- static dispatch_once_t sProxyAppDelegateRemoteNotificationOnceToken;
-
- #pragma mark - Public methods
-
- + (BOOL)isAppDelegateProxyEnabled {
- NSDictionary *infoDictionary = [NSBundle mainBundle].infoDictionary;
-
- id isFirebaseProxyEnabledPlistValue = infoDictionary[kGULFirebaseAppDelegateProxyEnabledPlistKey];
- id isGoogleProxyEnabledPlistValue =
- infoDictionary[kGULGoogleUtilitiesAppDelegateProxyEnabledPlistKey];
-
- // Enabled by default.
- BOOL isFirebaseAppDelegateProxyEnabled = YES;
- BOOL isGoogleUtilitiesAppDelegateProxyEnabled = YES;
-
- if ([isFirebaseProxyEnabledPlistValue isKindOfClass:[NSNumber class]]) {
- isFirebaseAppDelegateProxyEnabled = [isFirebaseProxyEnabledPlistValue boolValue];
- }
-
- if ([isGoogleProxyEnabledPlistValue isKindOfClass:[NSNumber class]]) {
- isGoogleUtilitiesAppDelegateProxyEnabled = [isGoogleProxyEnabledPlistValue boolValue];
- }
-
- // Only deactivate the proxy if it is explicitly disabled by app developers using either one of
- // the plist flags.
- return isFirebaseAppDelegateProxyEnabled && isGoogleUtilitiesAppDelegateProxyEnabled;
- }
-
- + (GULAppDelegateInterceptorID)registerAppDelegateInterceptor:
- (id<GULApplicationDelegate>)interceptor {
- NSAssert(interceptor, @"AppDelegateProxy cannot add nil interceptor");
- NSAssert([interceptor conformsToProtocol:@protocol(GULApplicationDelegate)],
- @"AppDelegateProxy interceptor does not conform to UIApplicationDelegate");
-
- if (!interceptor) {
- GULLogError(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");
- 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.");
- return nil;
- }
- GULZeroingWeakContainer *weakObject = [[GULZeroingWeakContainer alloc] init];
- weakObject.object = interceptor;
- [GULAppDelegateSwizzler interceptors][interceptorID] = weakObject;
- return interceptorID;
- }
-
- + (void)unregisterAppDelegateInterceptorWithID:(GULAppDelegateInterceptorID)interceptorID {
- NSAssert(interceptorID, @"AppDelegateProxy cannot unregister nil interceptor ID.");
- NSAssert(((NSString *)interceptorID).length != 0,
- @"AppDelegateProxy cannot unregister empty interceptor ID.");
-
- if (!interceptorID) {
- GULLogError(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);
- return;
- }
-
- [[GULAppDelegateSwizzler interceptors] removeObjectForKey:interceptorID];
- }
-
- + (void)proxyOriginalDelegate {
- if ([GULAppEnvironmentUtil isAppExtension]) {
- return;
- }
-
- dispatch_once(&sProxyAppDelegateOnceToken, ^{
- id<GULApplicationDelegate> originalDelegate =
- [GULAppDelegateSwizzler sharedApplication].delegate;
- [GULAppDelegateSwizzler proxyAppDelegate:originalDelegate];
- });
- }
-
- + (void)proxyOriginalDelegateIncludingAPNSMethods {
- if ([GULAppEnvironmentUtil isAppExtension]) {
- return;
- }
-
- [self proxyOriginalDelegate];
-
- dispatch_once(&sProxyAppDelegateRemoteNotificationOnceToken, ^{
- id<GULApplicationDelegate> appDelegate = [GULAppDelegateSwizzler sharedApplication].delegate;
-
- NSMutableDictionary *realImplementationsBySelector =
- [objc_getAssociatedObject(appDelegate, &kGULRealIMPBySelectorKey) mutableCopy];
-
- [self proxyRemoteNotificationsMethodsWithAppDelegateSubClass:gAppDelegateSubclass
- realClass:gOriginalAppDelegateClass
- appDelegate:appDelegate
- realImplementationsBySelector:realImplementationsBySelector];
-
- objc_setAssociatedObject(appDelegate, &kGULRealIMPBySelectorKey,
- [realImplementationsBySelector copy], OBJC_ASSOCIATION_RETAIN);
- [self reassignAppDelegate];
- });
- }
-
- #pragma mark - Create proxy
-
- + (GULApplication *)sharedApplication {
- if ([GULAppEnvironmentUtil isAppExtension]) {
- return nil;
- }
- id sharedApplication = nil;
- Class uiApplicationClass = NSClassFromString(kGULApplicationClassName);
- if (uiApplicationClass &&
- [uiApplicationClass respondsToSelector:(NSSelectorFromString(@"sharedApplication"))]) {
- sharedApplication = [uiApplicationClass sharedApplication];
- }
- return sharedApplication;
- }
-
- #pragma mark - Override default methods
-
- /** Creates a new subclass of the class of the given object and sets the isa value of the given
- * object to the new subclass. Additionally this copies methods to that new subclass that allow us
- * to intercept UIApplicationDelegate methods. This is better known as isa swizzling.
- *
- * @param appDelegate The object to which you want to isa swizzle. This has to conform to the
- * UIApplicationDelegate subclass.
- * @return Returns the new subclass.
- */
- + (nullable Class)createSubclassWithObject:(id<GULApplicationDelegate>)appDelegate {
- Class realClass = [appDelegate class];
-
- // Create GUL_<RealAppDelegate>_<UUID>
- NSString *classNameWithPrefix =
- [kGULAppDelegatePrefix stringByAppendingString:NSStringFromClass(realClass)];
- NSString *newClassName =
- [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);
- return nil;
- }
-
- // Register the new class as subclass of the real one. Do not allocate more than the real class
- // 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));
- return nil;
- }
-
- NSMutableDictionary<NSString *, NSValue *> *realImplementationsBySelector =
- [[NSMutableDictionary alloc] init];
-
- // For application:continueUserActivity:restorationHandler:
- SEL continueUserActivitySEL = @selector(application:continueUserActivity:restorationHandler:);
- [self proxyDestinationSelector:continueUserActivitySEL
- implementationsFromSourceSelector:continueUserActivitySEL
- fromClass:[GULAppDelegateSwizzler class]
- toClass:appDelegateSubClass
- realClass:realClass
- storeDestinationImplementationTo:realImplementationsBySelector];
-
- #if TARGET_OS_IOS || TARGET_OS_TV
- // Add the following methods from GULAppDelegate class, and store the real implementation so it
- // can forward to the real one.
- // For application:openURL:options:
- SEL applicationOpenURLOptionsSEL = @selector(application:openURL:options:);
- if ([appDelegate respondsToSelector:applicationOpenURLOptionsSEL]) {
- // Only add the application:openURL:options: method if the original AppDelegate implements it.
- // This fixes a bug if an app only implements application:openURL:sourceApplication:annotation:
- // (if we add the `options` method, iOS sees that one exists and does not call the
- // `sourceApplication` method, which in this case is the only one the app implements).
-
- [self proxyDestinationSelector:applicationOpenURLOptionsSEL
- implementationsFromSourceSelector:applicationOpenURLOptionsSEL
- fromClass:[GULAppDelegateSwizzler class]
- toClass:appDelegateSubClass
- realClass:realClass
- storeDestinationImplementationTo:realImplementationsBySelector];
- }
-
- // For application:handleEventsForBackgroundURLSession:completionHandler:
- SEL handleEventsForBackgroundURLSessionSEL = @selector(application:
- handleEventsForBackgroundURLSession:completionHandler:);
- [self proxyDestinationSelector:handleEventsForBackgroundURLSessionSEL
- implementationsFromSourceSelector:handleEventsForBackgroundURLSessionSEL
- fromClass:[GULAppDelegateSwizzler class]
- toClass:appDelegateSubClass
- realClass:realClass
- storeDestinationImplementationTo:realImplementationsBySelector];
- #endif // TARGET_OS_IOS || TARGET_OS_TV
-
- #if TARGET_OS_IOS
- // For application:openURL:sourceApplication:annotation:
- SEL openURLSourceApplicationAnnotationSEL = @selector(application:
- openURL:sourceApplication:annotation:);
-
- [self proxyDestinationSelector:openURLSourceApplicationAnnotationSEL
- implementationsFromSourceSelector:openURLSourceApplicationAnnotationSEL
- fromClass:[GULAppDelegateSwizzler class]
- toClass:appDelegateSubClass
- realClass:realClass
- storeDestinationImplementationTo:realImplementationsBySelector];
- #endif // TARGET_OS_IOS
-
- // Override the description too so the custom class name will not show up.
- [GULAppDelegateSwizzler addInstanceMethodWithDestinationSelector:@selector(description)
- withImplementationFromSourceSelector:@selector(fakeDescription)
- fromClass:[self class]
- toClass:appDelegateSubClass];
-
- // Store original implementations to a fake property of the original delegate.
- objc_setAssociatedObject(appDelegate, &kGULRealIMPBySelectorKey,
- [realImplementationsBySelector copy], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
- objc_setAssociatedObject(appDelegate, &kGULRealClassKey, realClass,
- OBJC_ASSOCIATION_RETAIN_NONATOMIC);
-
- // The subclass size has to be exactly the same size with the original class size. The subclass
- // 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));
- NSAssert(NO, @"Classes must be the same size to swizzle isa");
- return nil;
- }
-
- // 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]);
- }
-
- return appDelegateSubClass;
- }
-
- + (void)proxyRemoteNotificationsMethodsWithAppDelegateSubClass:(Class)appDelegateSubClass
- realClass:(Class)realClass
- appDelegate:(id)appDelegate
- realImplementationsBySelector:
- (NSMutableDictionary *)realImplementationsBySelector {
- if (realClass == nil || appDelegateSubClass == nil || appDelegate == nil ||
- realImplementationsBySelector == nil) {
- // The App Delegate has not been swizzled.
- return;
- }
-
- // For application:didRegisterForRemoteNotificationsWithDeviceToken:
- SEL didRegisterForRemoteNotificationsSEL =
- NSSelectorFromString(kGULDidRegisterForRemoteNotificationsSEL);
- SEL didRegisterForRemoteNotificationsDonorSEL = @selector(application:
- donor_didRegisterForRemoteNotificationsWithDeviceToken:);
-
- [self proxyDestinationSelector:didRegisterForRemoteNotificationsSEL
- implementationsFromSourceSelector:didRegisterForRemoteNotificationsDonorSEL
- fromClass:[GULAppDelegateSwizzler class]
- toClass:appDelegateSubClass
- realClass:realClass
- storeDestinationImplementationTo:realImplementationsBySelector];
-
- // For application:didFailToRegisterForRemoteNotificationsWithError:
- SEL didFailToRegisterForRemoteNotificationsSEL =
- NSSelectorFromString(kGULDidFailToRegisterForRemoteNotificationsSEL);
- SEL didFailToRegisterForRemoteNotificationsDonorSEL = @selector(application:
- donor_didFailToRegisterForRemoteNotificationsWithError:);
-
- [self proxyDestinationSelector:didFailToRegisterForRemoteNotificationsSEL
- implementationsFromSourceSelector:didFailToRegisterForRemoteNotificationsDonorSEL
- fromClass:[GULAppDelegateSwizzler class]
- toClass:appDelegateSubClass
- realClass:realClass
- storeDestinationImplementationTo:realImplementationsBySelector];
-
- // For application:didReceiveRemoteNotification:
- SEL didReceiveRemoteNotificationSEL = NSSelectorFromString(kGULDidReceiveRemoteNotificationSEL);
- SEL didReceiveRemoteNotificationDonotSEL = @selector(application:
- donor_didReceiveRemoteNotification:);
-
- [self proxyDestinationSelector:didReceiveRemoteNotificationSEL
- implementationsFromSourceSelector:didReceiveRemoteNotificationDonotSEL
- fromClass:[GULAppDelegateSwizzler class]
- toClass:appDelegateSubClass
- realClass:realClass
- storeDestinationImplementationTo:realImplementationsBySelector];
-
- // For application:didReceiveRemoteNotification:fetchCompletionHandler:
- #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000
- if ([GULAppEnvironmentUtil isIOS7OrHigher]) {
- 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];
- }
- }
- #endif // __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000
- }
-
- /// We have to do this to invalidate the cache that caches the original respondsToSelector of
- /// openURL handlers. Without this, it won't call the default implementations because the system
- /// checks and caches them.
- /// Register KVO only once. Otherwise, the observing method will be called as many times as
- /// being registered.
- + (void)reassignAppDelegate {
- id<GULApplicationDelegate> delegate = [self sharedApplication].delegate;
- [self sharedApplication].delegate = nil;
- [self sharedApplication].delegate = delegate;
- gOriginalAppDelegate = delegate;
- [[GULAppDelegateObserver sharedInstance] observeUIApplication];
- }
-
- #pragma mark - Helper methods
-
- + (GULMutableDictionary *)interceptors {
- static dispatch_once_t onceToken;
- static GULMutableDictionary *sInterceptors;
- dispatch_once(&onceToken, ^{
- sInterceptors = [[GULMutableDictionary alloc] init];
- });
- return sInterceptors;
- }
-
- + (nullable NSValue *)originalImplementationForSelector:(SEL)selector object:(id)object {
- NSDictionary *realImplementationBySelector =
- objc_getAssociatedObject(object, &kGULRealIMPBySelectorKey);
- return realImplementationBySelector[NSStringFromSelector(selector)];
- }
-
- + (void)proxyDestinationSelector:(SEL)destinationSelector
- implementationsFromSourceSelector:(SEL)sourceSelector
- fromClass:(Class)sourceClass
- toClass:(Class)destinationClass
- realClass:(Class)realClass
- storeDestinationImplementationTo:
- (NSMutableDictionary<NSString *, NSValue *> *)destinationImplementationsBySelector {
- [self addInstanceMethodWithDestinationSelector:destinationSelector
- withImplementationFromSourceSelector:sourceSelector
- fromClass:sourceClass
- toClass:destinationClass];
- IMP sourceImplementation =
- [GULAppDelegateSwizzler implementationOfMethodSelector:destinationSelector
- fromClass:realClass];
- NSValue *sourceImplementationPointer = [NSValue valueWithPointer:sourceImplementation];
-
- NSString *destinationSelectorString = NSStringFromSelector(destinationSelector);
- destinationImplementationsBySelector[destinationSelectorString] = sourceImplementationPointer;
- }
-
- /** Copies a method identified by the methodSelector from one class to the other. After this method
- * is called, performing [toClassInstance methodSelector] will be similar to calling
- * [fromClassInstance methodSelector]. This method does nothing if toClass already has a method
- * identified by methodSelector.
- *
- * @param methodSelector The SEL that identifies both the method on the fromClass as well as the
- * one on the toClass.
- * @param fromClass The class from which a method is sourced.
- * @param toClass The class to which the method is added. If the class already has a method with
- * the same selector, this has no effect.
- */
- + (void)addInstanceMethodWithSelector:(SEL)methodSelector
- fromClass:(Class)fromClass
- toClass:(Class)toClass {
- [self addInstanceMethodWithDestinationSelector:methodSelector
- withImplementationFromSourceSelector:methodSelector
- fromClass:fromClass
- toClass:toClass];
- }
-
- /** Copies a method identified by the sourceSelector from the fromClass as a method for the
- * destinationSelector on the toClass. After this method is called, performing
- * [toClassInstance destinationSelector] will be similar to calling
- * [fromClassInstance sourceSelector]. This method does nothing if toClass already has a method
- * identified by destinationSelector.
- *
- * @param destinationSelector The SEL that identifies the method on the toClass.
- * @param sourceSelector The SEL that identifies the method on the fromClass.
- * @param fromClass The class from which a method is sourced.
- * @param toClass The class to which the method is added. If the class already has a method with
- * the same selector, this has no effect.
- */
- + (void)addInstanceMethodWithDestinationSelector:(SEL)destinationSelector
- withImplementationFromSourceSelector:(SEL)sourceSelector
- fromClass:(Class)fromClass
- toClass:(Class)toClass {
- Method method = class_getInstanceMethod(fromClass, sourceSelector);
- 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));
- }
- }
-
- /** Gets the IMP of the instance method on the class identified by the selector.
- *
- * @param selector The selector of which the IMP is to be fetched.
- * @param aClass The class from which the IMP is to be fetched.
- * @return The IMP of the instance method identified by selector and aClass.
- */
- + (IMP)implementationOfMethodSelector:(SEL)selector fromClass:(Class)aClass {
- Method aMethod = class_getInstanceMethod(aClass, selector);
- return method_getImplementation(aMethod);
- }
-
- /** Enumerates through all the interceptors and if they respond to a given selector, executes a
- * GULAppDelegateInterceptorCallback with the interceptor.
- *
- * @param methodSelector The SEL to check if an interceptor responds to.
- * @param callback the GULAppDelegateInterceptorCallback.
- */
- + (void)notifyInterceptorsWithMethodSelector:(SEL)methodSelector
- callback:(GULAppDelegateInterceptorCallback)callback {
- if (!callback) {
- return;
- }
-
- NSDictionary *interceptors = [GULAppDelegateSwizzler interceptors].dictionary;
- [interceptors enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
- GULZeroingWeakContainer *interceptorContainer = obj;
- id interceptor = interceptorContainer.object;
- if (!interceptor) {
- GULLogWarning(
- kGULLoggerSwizzler, NO,
- [NSString
- stringWithFormat:@"I-SWZ%06ld", (long)kGULSwizzlerMessageCodeAppDelegateSwizzling010],
- @"AppDelegateProxy cannot find interceptor with ID %@. Removing the interceptor.", key);
- [[GULAppDelegateSwizzler interceptors] removeObjectForKey:key];
- return;
- }
- if ([interceptor respondsToSelector:methodSelector]) {
- callback(interceptor);
- }
- }];
- }
-
- // The methods below are donor methods which are added to the dynamic subclass of the App Delegate.
- // They are called within the scope of the real App Delegate so |self| does not refer to the
- // GULAppDelegateSwizzler instance but the real App Delegate instance.
-
- #pragma mark - [Donor Methods] Overridden instance description method
-
- - (NSString *)fakeDescription {
- Class realClass = objc_getAssociatedObject(self, &kGULRealClassKey);
- return [NSString stringWithFormat:@"<%@: %p>", realClass, self];
- }
-
- #pragma mark - [Donor Methods] URL overridden handler methods
- #if TARGET_OS_IOS || TARGET_OS_TV
-
- - (BOOL)application:(GULApplication *)application
- openURL:(NSURL *)url
- options:(NSDictionary<NSString *, id> *)options {
- SEL methodSelector = @selector(application:openURL:options:);
- // Call the real implementation if the real App Delegate has any.
- NSValue *openURLIMPPointer =
- [GULAppDelegateSwizzler originalImplementationForSelector:methodSelector object:self];
- GULRealOpenURLOptionsIMP openURLOptionsIMP = [openURLIMPPointer pointerValue];
-
- __block BOOL returnedValue = NO;
-
- // This is needed to for the library to be warning free on iOS versions < 9.
- #pragma clang diagnostic push
- #pragma clang diagnostic ignored "-Wunguarded-availability"
- [GULAppDelegateSwizzler
- notifyInterceptorsWithMethodSelector:methodSelector
- callback:^(id<GULApplicationDelegate> interceptor) {
- returnedValue |= [interceptor application:application
- openURL:url
- options:options];
- }];
- #pragma clang diagnostic pop
- if (openURLOptionsIMP) {
- returnedValue |= openURLOptionsIMP(self, methodSelector, application, url, options);
- }
- return returnedValue;
- }
-
- #endif // TARGET_OS_IOS || TARGET_OS_TV
-
- #if TARGET_OS_IOS
-
- - (BOOL)application:(GULApplication *)application
- openURL:(NSURL *)url
- sourceApplication:(NSString *)sourceApplication
- annotation:(id)annotation {
- SEL methodSelector = @selector(application:openURL:sourceApplication:annotation:);
-
- // Call the real implementation if the real App Delegate has any.
- NSValue *openURLSourceAppAnnotationIMPPointer =
- [GULAppDelegateSwizzler originalImplementationForSelector:methodSelector object:self];
- GULRealOpenURLSourceApplicationAnnotationIMP openURLSourceApplicationAnnotationIMP =
- [openURLSourceAppAnnotationIMPPointer pointerValue];
-
- __block BOOL returnedValue = NO;
- [GULAppDelegateSwizzler
- notifyInterceptorsWithMethodSelector:methodSelector
- callback:^(id<GULApplicationDelegate> interceptor) {
- #pragma clang diagnostic push
- #pragma clang diagnostic ignored "-Wdeprecated-declarations"
- returnedValue |= [interceptor application:application
- openURL:url
- sourceApplication:sourceApplication
- annotation:annotation];
- #pragma clang diagnostic pop
- }];
- if (openURLSourceApplicationAnnotationIMP) {
- returnedValue |= openURLSourceApplicationAnnotationIMP(self, methodSelector, application, url,
- sourceApplication, annotation);
- }
- return returnedValue;
- }
-
- #endif // TARGET_OS_IOS
-
- #pragma mark - [Donor Methods] Network overridden handler methods
-
- #if TARGET_OS_IOS || TARGET_OS_TV
-
- #pragma clang diagnostic push
- #pragma clang diagnostic ignored "-Wstrict-prototypes"
- - (void)application:(GULApplication *)application
- handleEventsForBackgroundURLSession:(NSString *)identifier
- completionHandler:(void (^)())completionHandler API_AVAILABLE(ios(7.0)) {
- #pragma clang diagnostic pop
- SEL methodSelector = @selector(application:
- handleEventsForBackgroundURLSession:completionHandler:);
- NSValue *handleBackgroundSessionPointer =
- [GULAppDelegateSwizzler originalImplementationForSelector:methodSelector object:self];
- GULRealHandleEventsForBackgroundURLSessionIMP handleBackgroundSessionIMP =
- [handleBackgroundSessionPointer pointerValue];
-
- // Notify interceptors.
- [GULAppDelegateSwizzler
- notifyInterceptorsWithMethodSelector:methodSelector
- callback:^(id<GULApplicationDelegate> interceptor) {
- [interceptor application:application
- handleEventsForBackgroundURLSession:identifier
- completionHandler:completionHandler];
- }];
- // Call the real implementation if the real App Delegate has any.
- if (handleBackgroundSessionIMP) {
- handleBackgroundSessionIMP(self, methodSelector, application, identifier, completionHandler);
- }
- }
-
- #endif // TARGET_OS_IOS || TARGET_OS_TV
-
- #pragma mark - [Donor Methods] User Activities overridden handler methods
-
- // This is needed to for the library to be warning free on iOS versions < 8.
- #pragma clang diagnostic push
- #pragma clang diagnostic ignored "-Wunguarded-availability"
- - (BOOL)application:(GULApplication *)application
- continueUserActivity:(NSUserActivity *)userActivity
- restorationHandler:(void (^)(NSArray *restorableObjects))restorationHandler {
- SEL methodSelector = @selector(application:continueUserActivity:restorationHandler:);
- NSValue *continueUserActivityIMPPointer =
- [GULAppDelegateSwizzler originalImplementationForSelector:methodSelector object:self];
- GULRealContinueUserActivityIMP continueUserActivityIMP =
- continueUserActivityIMPPointer.pointerValue;
-
- __block BOOL returnedValue = NO;
- [GULAppDelegateSwizzler
- notifyInterceptorsWithMethodSelector:methodSelector
- callback:^(id<GULApplicationDelegate> interceptor) {
- returnedValue |= [interceptor application:application
- continueUserActivity:userActivity
- restorationHandler:restorationHandler];
- }];
- // Call the real implementation if the real App Delegate has any.
- if (continueUserActivityIMP) {
- returnedValue |= continueUserActivityIMP(self, methodSelector, application, userActivity,
- restorationHandler);
- }
- return returnedValue;
- }
- #pragma clang diagnostic pop
-
- #pragma mark - [Donor Methods] Remote Notifications
-
- - (void)application:(GULApplication *)application
- donor_didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
- SEL methodSelector = NSSelectorFromString(kGULDidRegisterForRemoteNotificationsSEL);
-
- NSValue *didRegisterForRemoteNotificationsIMPPointer =
- [GULAppDelegateSwizzler originalImplementationForSelector:methodSelector object:self];
- GULRealDidRegisterForRemoteNotificationsIMP didRegisterForRemoteNotificationsIMP =
- [didRegisterForRemoteNotificationsIMPPointer pointerValue];
-
- // Notify interceptors.
- [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 *)(&deviceToken) atIndex:3];
- [invocation invoke];
- }];
- // Call the real implementation if the real App Delegate has any.
- if (didRegisterForRemoteNotificationsIMP) {
- didRegisterForRemoteNotificationsIMP(self, methodSelector, application, deviceToken);
- }
- }
-
- - (void)application:(GULApplication *)application
- donor_didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
- SEL methodSelector = NSSelectorFromString(kGULDidFailToRegisterForRemoteNotificationsSEL);
- NSValue *didFailToRegisterForRemoteNotificationsIMPPointer =
- [GULAppDelegateSwizzler originalImplementationForSelector:methodSelector object:self];
- GULRealDidFailToRegisterForRemoteNotificationsIMP didFailToRegisterForRemoteNotificationsIMP =
- [didFailToRegisterForRemoteNotificationsIMPPointer pointerValue];
-
- // Notify interceptors.
- [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 *)(&error) atIndex:3];
- [invocation invoke];
- }];
- // Call the real implementation if the real App Delegate has any.
- if (didFailToRegisterForRemoteNotificationsIMP) {
- didFailToRegisterForRemoteNotificationsIMP(self, methodSelector, application, error);
- }
- }
-
- #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000
- // This is needed to for the library to be warning free on iOS versions < 7.
- #pragma clang diagnostic push
- #pragma clang diagnostic ignored "-Wunguarded-availability"
- - (void)application:(GULApplication *)application
- donor_didReceiveRemoteNotification:(NSDictionary *)userInfo
- fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
- SEL methodSelector = NSSelectorFromString(kGULDidReceiveRemoteNotificationWithCompletionSEL);
- NSValue *didReceiveRemoteNotificationWithCompletionIMPPointer =
- [GULAppDelegateSwizzler originalImplementationForSelector:methodSelector object:self];
- GULRealDidReceiveRemoteNotificationWithCompletionIMP
- didReceiveRemoteNotificationWithCompletionIMP =
- [didReceiveRemoteNotificationWithCompletionIMPPointer pointerValue];
-
- // Notify interceptors.
- [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 setArgument:(void *)(&completionHandler) atIndex:4];
- [invocation invoke];
- }];
- // Call the real implementation if the real App Delegate has any.
- if (didReceiveRemoteNotificationWithCompletionIMP) {
- didReceiveRemoteNotificationWithCompletionIMP(self, methodSelector, application, userInfo,
- completionHandler);
- }
- }
- #pragma clang diagnostic pop
- #endif // __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000
-
- - (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);
- if (methodDescription.types == NULL) {
- return nil;
- }
-
- NSMethodSignature *signature = [NSMethodSignature signatureWithObjCTypes:methodDescription.types];
- return [NSInvocation invocationWithMethodSignature:signature];
- }
-
- + (void)proxyAppDelegate:(id<GULApplicationDelegate>)appDelegate {
- if (![appDelegate conformsToProtocol:@protocol(GULApplicationDelegate)]) {
- GULLogNotice(
- kGULLoggerSwizzler, NO,
- [NSString
- stringWithFormat:@"I-SWZ%06ld",
- (long)kGULSwizzlerMessageCodeAppDelegateSwizzlingInvalidAppDelegate],
- @"App Delegate does not conform to UIApplicationDelegate protocol. %@",
- [GULAppDelegateSwizzler correctAlternativeWhenAppDelegateProxyNotCreated]);
- return;
- }
-
- 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]);
- 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]);
- return;
- }
-
- @try {
- gOriginalAppDelegateClass = [originalDelegate class];
- 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]);
- return;
- }
- }
-
- #pragma mark - Methods to print correct debug logs
-
- + (NSString *)correctAppDelegateProxyKey {
- return NSClassFromString(@"FIRCore") ? kGULFirebaseAppDelegateProxyEnabledPlistKey
- : kGULGoogleUtilitiesAppDelegateProxyEnabledPlistKey;
- }
-
- + (NSString *)correctAlternativeWhenAppDelegateProxyNotCreated {
- return NSClassFromString(@"FIRCore")
- ? @"To log deep link campaigns manually, call the methods in "
- @"FIRAnalytics+AppDelegate.h."
- : @"";
- }
-
- #pragma mark - Private Methods for Testing
-
- + (void)clearInterceptors {
- [[self interceptors] removeAllObjects];
- }
-
- + (void)resetProxyOriginalDelegateOnceToken {
- sProxyAppDelegateOnceToken = 0;
- sProxyAppDelegateRemoteNotificationOnceToken = 0;
- }
-
- + (id<GULApplicationDelegate>)originalDelegate {
- return gOriginalAppDelegate;
- }
-
- @end
|