// 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" #if TARGET_OS_IOS #import #import #import #import "../Common/GULLoggerCodes.h" #import "Internal/GULAppDelegateSwizzler_Private.h" #import "Private/GULAppDelegateSwizzler.h" #import #import // 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, UIApplication *, NSURL *, NSString *, id); typedef BOOL (*GULRealOpenURLOptionsIMP)( id, SEL, UIApplication *, NSURL *, NSDictionary *); #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wstrict-prototypes" typedef void (*GULRealHandleEventsForBackgroundURLSessionIMP)( id, SEL, UIApplication *, 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, UIApplication *, NSUserActivity *, void (^)(NSArray *restorableObjects)); #pragma clang diagnostic pop typedef void (^GULAppDelegateInterceptorCallback)(id); // The strings below are the keys for associated objects. static char const *const kGULContinueUserActivityIMPKey = "GUL_continueUserActivityIMP"; static char const *const kGULHandleBackgroundSessionIMPKey = "GUL_handleBackgroundSessionIMP"; static char const *const kGULOpenURLOptionsIMPKey = "GUL_openURLOptionsIMP"; static char const *const kGULOpenURLOptionsSourceAnnotationsIMPKey = "GUL_openURLSourceApplicationAnnotationIMP"; 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 gOriginalAppDelegate; /** * 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; #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)interceptor { NSAssert(interceptor, @"AppDelegateProxy cannot add nil interceptor"); NSAssert([interceptor conformsToProtocol:@protocol(UIApplicationDelegate)], @"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(UIApplicationDelegate)]) { 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 { dispatch_once(&sProxyAppDelegateOnceToken, ^{ id originalDelegate = [GULAppDelegateSwizzler sharedApplication].delegate; [GULAppDelegateSwizzler proxyAppDelegate:originalDelegate]; }); } #pragma mark - Create proxy + (UIApplication *)sharedApplication { if ([GULAppEnvironmentUtil isAppExtension]) { return nil; } id sharedApplication = nil; Class uiApplicationClass = NSClassFromString(@"UIApplication"); 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 anObject The object to which you want to isa swizzle. This has to conform to the * UIApplicationDelegate subclass. */ + (void)createSubclassWithObject:(id)anObject { Class realClass = [anObject class]; // Create GUL__ NSString *classNameWithPrefix = [kGULAppDelegatePrefix stringByAppendingString:NSStringFromClass(realClass)]; NSTimeInterval timestamp = [NSDate date].timeIntervalSince1970; NSString *newClassName = [NSString stringWithFormat:@"%@-%0.0f", classNameWithPrefix, timestamp * 1000]; 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; } // 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; } // Add the following methods from GULAppDelegate class, and store the real implementation so it // can forward to the real one. // For application:openURL:options: NSValue *openURLOptionsIMPPointer; SEL applicationOpenURLOptionsSEL = @selector(application:openURL:options:); if ([anObject 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). [GULAppDelegateSwizzler addInstanceMethodWithSelector:applicationOpenURLOptionsSEL fromClass:[GULAppDelegateSwizzler class] toClass:appDelegateSubClass]; GULRealOpenURLOptionsIMP openURLOptionsIMP = (GULRealOpenURLOptionsIMP) [GULAppDelegateSwizzler implementationOfMethodSelector:applicationOpenURLOptionsSEL fromClass:realClass]; openURLOptionsIMPPointer = [NSValue valueWithPointer:openURLOptionsIMP]; } // For application:continueUserActivity:restorationHandler: SEL continueUserActivitySEL = @selector(application:continueUserActivity:restorationHandler:); [GULAppDelegateSwizzler addInstanceMethodWithSelector:continueUserActivitySEL fromClass:[GULAppDelegateSwizzler class] toClass:appDelegateSubClass]; GULRealContinueUserActivityIMP continueUserActivityIMP = (GULRealContinueUserActivityIMP) [GULAppDelegateSwizzler implementationOfMethodSelector:continueUserActivitySEL fromClass:realClass]; NSValue *continueUserActivityIMPPointer = [NSValue valueWithPointer:continueUserActivityIMP]; // For application:openURL:sourceApplication:annotation: SEL openURLSourceApplicationAnnotationSEL = @selector(application:openURL:sourceApplication:annotation:); [GULAppDelegateSwizzler addInstanceMethodWithSelector:openURLSourceApplicationAnnotationSEL fromClass:[GULAppDelegateSwizzler class] toClass:appDelegateSubClass]; GULRealOpenURLSourceApplicationAnnotationIMP openURLSourceApplicationAnnotationIMP = (GULRealOpenURLSourceApplicationAnnotationIMP)[GULAppDelegateSwizzler implementationOfMethodSelector:openURLSourceApplicationAnnotationSEL fromClass:realClass]; NSValue *openURLSourceAppAnnotationIMPPointer = [NSValue valueWithPointer:openURLSourceApplicationAnnotationIMP]; // For application:handleEventsForBackgroundURLSession:completionHandler: SEL handleEventsForBackgroundURLSessionSEL = @selector(application:handleEventsForBackgroundURLSession:completionHandler:); [GULAppDelegateSwizzler addInstanceMethodWithSelector:handleEventsForBackgroundURLSessionSEL fromClass:[GULAppDelegateSwizzler class] toClass:appDelegateSubClass]; GULRealHandleEventsForBackgroundURLSessionIMP handleBackgroundSessionIMP = (GULRealHandleEventsForBackgroundURLSessionIMP)[GULAppDelegateSwizzler implementationOfMethodSelector:handleEventsForBackgroundURLSessionSEL fromClass:realClass]; NSValue *handleBackgroundSessionIMPPointer = [NSValue valueWithPointer:handleBackgroundSessionIMP]; // 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]; // Create fake properties for the real app delegate object. objc_setAssociatedObject(anObject, &kGULContinueUserActivityIMPKey, continueUserActivityIMPPointer, OBJC_ASSOCIATION_RETAIN_NONATOMIC); objc_setAssociatedObject(anObject, &kGULHandleBackgroundSessionIMPKey, handleBackgroundSessionIMPPointer, OBJC_ASSOCIATION_RETAIN_NONATOMIC); if (openURLOptionsIMPPointer) { objc_setAssociatedObject(anObject, &kGULOpenURLOptionsIMPKey, openURLOptionsIMPPointer, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } objc_setAssociatedObject(anObject, &kGULOpenURLOptionsSourceAnnotationsIMPKey, openURLSourceAppAnnotationIMPPointer, OBJC_ASSOCIATION_RETAIN_NONATOMIC); objc_setAssociatedObject(anObject, &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; } // Make the newly created class to be the subclass of the real App Delegate class. objc_registerClassPair(appDelegateSubClass); if (object_setClass(anObject, 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]); } // 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. id delegate = [GULAppDelegateSwizzler sharedApplication].delegate; [GULAppDelegateSwizzler sharedApplication].delegate = nil; [GULAppDelegateSwizzler 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; } /** 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 - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionary *)options { // Call the real implementation if the real App Delegate has any. NSValue *openURLIMPPointer = objc_getAssociatedObject(self, &kGULOpenURLOptionsIMPKey); GULRealOpenURLOptionsIMP openURLOptionsIMP = [openURLIMPPointer pointerValue]; __block BOOL returnedValue = NO; SEL methodSelector = @selector(application:openURL:options:); // 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 interceptor) { returnedValue |= [interceptor application:application openURL:url options:options]; }]; #pragma clang diagnostic pop if (openURLOptionsIMP) { returnedValue |= openURLOptionsIMP(self, methodSelector, application, url, options); } return returnedValue; } - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation { // Call the real implementation if the real App Delegate has any. NSValue *openURLSourceAppAnnotationIMPPointer = objc_getAssociatedObject(self, &kGULOpenURLOptionsSourceAnnotationsIMPKey); GULRealOpenURLSourceApplicationAnnotationIMP openURLSourceApplicationAnnotationIMP = [openURLSourceAppAnnotationIMPPointer pointerValue]; __block BOOL returnedValue = NO; SEL methodSelector = @selector(application:openURL:sourceApplication:annotation:); [GULAppDelegateSwizzler notifyInterceptorsWithMethodSelector:methodSelector callback:^(id 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; } #pragma mark - [Donor Methods] Network overridden handler methods #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wstrict-prototypes" - (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)())completionHandler API_AVAILABLE(ios(7.0)) { #pragma clang diagnostic pop NSValue *handleBackgroundSessionPointer = objc_getAssociatedObject(self, &kGULHandleBackgroundSessionIMPKey); GULRealHandleEventsForBackgroundURLSessionIMP handleBackgroundSessionIMP = [handleBackgroundSessionPointer pointerValue]; // Notify interceptors. SEL methodSelector = @selector(application:handleEventsForBackgroundURLSession:completionHandler:); [GULAppDelegateSwizzler notifyInterceptorsWithMethodSelector:methodSelector callback:^(id 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); } } #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:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray *restorableObjects))restorationHandler { NSValue *continueUserActivityIMPPointer = objc_getAssociatedObject(self, &kGULContinueUserActivityIMPKey); GULRealContinueUserActivityIMP continueUserActivityIMP = continueUserActivityIMPPointer.pointerValue; __block BOOL returnedValue = NO; SEL methodSelector = @selector(application:continueUserActivity:restorationHandler:); [GULAppDelegateSwizzler notifyInterceptorsWithMethodSelector:methodSelector callback:^(id 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 + (void)proxyAppDelegate:(id)appDelegate { id 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 { [self createSubclassWithObject:originalDelegate]; } @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 #ifdef GUL_APP_DELEGATE_TESTING + (void)clearInterceptors { [[self interceptors] removeAllObjects]; } + (void)resetProxyOriginalDelegateOnceToken { sProxyAppDelegateOnceToken = 0; } + (id)originalDelegate { return gOriginalAppDelegate; } #endif // GUL_APP_DELEGATE_TESTING @end #endif // TARGET_OS_IOS