AutorÃa | Ultima modificación | Ver Log |
// 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/AppDelegateSwizzler/Internal/GULAppDelegateSwizzler_Private.h"#import "GoogleUtilities/AppDelegateSwizzler/Public/GoogleUtilities/GULAppDelegateSwizzler.h"#import "GoogleUtilities/Common/GULLoggerCodes.h"#import "GoogleUtilities/Environment/Public/GoogleUtilities/GULAppEnvironmentUtil.h"#import "GoogleUtilities/Logger/Public/GoogleUtilities/GULLogger.h"#import "GoogleUtilities/Network/Public/GoogleUtilities/GULMutableDictionary.h"#import <dispatch/group.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 poptypedef BOOL (*GULRealContinueUserActivityIMP)(id, SEL, GULApplication *, NSUserActivity *, void (^)(NSArray *restorableObjects));typedef void (*GULRealDidRegisterForRemoteNotificationsIMP)(id, SEL, GULApplication *, NSData *);typedef void (*GULRealDidFailToRegisterForRemoteNotificationsIMP)(id,SEL,GULApplication *,NSError *);typedef void (*GULRealDidReceiveRemoteNotificationIMP)(id, SEL, GULApplication *, NSDictionary *);#if !TARGET_OS_WATCH && !TARGET_OS_OSXtypedef void (*GULRealDidReceiveRemoteNotificationWithCompletionIMP)(id, SEL, GULApplication *, NSDictionary *, void (^)(UIBackgroundFetchResult));#endif // !TARGET_OS_WATCH && !TARGET_OS_OSXtypedef 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 and Scene Delegate Proxying. */static NSString *const kGULFirebaseAppDelegateProxyEnabledPlistKey =@"FirebaseAppDelegateProxyEnabled";/** Plist key that allows developers not using Firebase to disable App and Scene 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:selfforKeyPath:kGULAppDelegateKeyPathoptions:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOldcontext:nil];_isObserving = YES;}- (void)observeValueForKeyPath:(NSString *)keyPathofObject:(id)objectchange:(NSDictionary *)changecontext:(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:selfforKeyPath:kGULAppDelegateKeyPath];_isObserving = NO;}}}@end@implementation GULAppDelegateSwizzlerstatic 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:gAppDelegateSubclassrealClass:gOriginalAppDelegateClassappDelegate:appDelegaterealImplementationsBySelector: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:continueUserActivitySELimplementationsFromSourceSelector:continueUserActivitySELfromClass:[GULAppDelegateSwizzler class]toClass:appDelegateSubClassrealClass:realClassstoreDestinationImplementationTo: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:applicationOpenURLOptionsSELimplementationsFromSourceSelector:applicationOpenURLOptionsSELfromClass:[GULAppDelegateSwizzler class]toClass:appDelegateSubClassrealClass:realClassstoreDestinationImplementationTo:realImplementationsBySelector];}// For application:handleEventsForBackgroundURLSession:completionHandler:SEL handleEventsForBackgroundURLSessionSEL = @selector(application:handleEventsForBackgroundURLSession:completionHandler:);[self proxyDestinationSelector:handleEventsForBackgroundURLSessionSELimplementationsFromSourceSelector:handleEventsForBackgroundURLSessionSELfromClass:[GULAppDelegateSwizzler class]toClass:appDelegateSubClassrealClass:realClassstoreDestinationImplementationTo: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:openURLSourceApplicationAnnotationSELimplementationsFromSourceSelector:openURLSourceApplicationAnnotationSELfromClass:[GULAppDelegateSwizzler class]toClass:appDelegateSubClassrealClass:realClassstoreDestinationImplementationTo: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)appDelegateSubClassrealClass:(Class)realClassappDelegate:(id)appDelegaterealImplementationsBySelector:(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:didRegisterForRemoteNotificationsSELimplementationsFromSourceSelector:didRegisterForRemoteNotificationsDonorSELfromClass:[GULAppDelegateSwizzler class]toClass:appDelegateSubClassrealClass:realClassstoreDestinationImplementationTo:realImplementationsBySelector];// For application:didFailToRegisterForRemoteNotificationsWithError:SEL didFailToRegisterForRemoteNotificationsSEL =NSSelectorFromString(kGULDidFailToRegisterForRemoteNotificationsSEL);SEL didFailToRegisterForRemoteNotificationsDonorSEL = @selector(application:donor_didFailToRegisterForRemoteNotificationsWithError:);[self proxyDestinationSelector:didFailToRegisterForRemoteNotificationsSELimplementationsFromSourceSelector:didFailToRegisterForRemoteNotificationsDonorSELfromClass:[GULAppDelegateSwizzler class]toClass:appDelegateSubClassrealClass:realClassstoreDestinationImplementationTo:realImplementationsBySelector];// For application:didReceiveRemoteNotification:SEL didReceiveRemoteNotificationSEL = NSSelectorFromString(kGULDidReceiveRemoteNotificationSEL);SEL didReceiveRemoteNotificationDonotSEL = @selector(application:donor_didReceiveRemoteNotification:);[self proxyDestinationSelector:didReceiveRemoteNotificationSELimplementationsFromSourceSelector:didReceiveRemoteNotificationDonotSELfromClass:[GULAppDelegateSwizzler class]toClass:appDelegateSubClassrealClass:realClassstoreDestinationImplementationTo:realImplementationsBySelector];// For application:didReceiveRemoteNotification:fetchCompletionHandler:#if !TARGET_OS_WATCH && !TARGET_OS_OSXSEL 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:didReceiveRemoteNotificationWithCompletionSELimplementationsFromSourceSelector:didReceiveRemoteNotificationWithCompletionDonorSELfromClass:[GULAppDelegateSwizzler class]toClass:appDelegateSubClassrealClass:realClassstoreDestinationImplementationTo:realImplementationsBySelector];}#endif // !TARGET_OS_WATCH && !TARGET_OS_OSX}/// 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 {#if !TARGET_OS_WATCHid<GULApplicationDelegate> delegate = [self sharedApplication].delegate;[self sharedApplication].delegate = nil;[self sharedApplication].delegate = delegate;gOriginalAppDelegate = delegate;[[GULAppDelegateObserver sharedInstance] observeUIApplication];#endif}#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)destinationSelectorimplementationsFromSourceSelector:(SEL)sourceSelectorfromClass:(Class)sourceClasstoClass:(Class)destinationClassrealClass:(Class)realClassstoreDestinationImplementationTo:(NSMutableDictionary<NSString *, NSValue *> *)destinationImplementationsBySelector {[self addInstanceMethodWithDestinationSelector:destinationSelectorwithImplementationFromSourceSelector:sourceSelectorfromClass:sourceClasstoClass:destinationClass];IMP sourceImplementation =[GULAppDelegateSwizzler implementationOfMethodSelector:destinationSelectorfromClass: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)methodSelectorfromClass:(Class)fromClasstoClass:(Class)toClass {[self addInstanceMethodWithDestinationSelector:methodSelectorwithImplementationFromSourceSelector:methodSelectorfromClass:fromClasstoClass: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)destinationSelectorwithImplementationFromSourceSelector:(SEL)sourceSelectorfromClass:(Class)fromClasstoClass:(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)methodSelectorcallback:(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,[NSStringstringWithFormat:@"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 *)applicationopenURL:(NSURL *)urloptions:(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"[GULAppDelegateSwizzlernotifyInterceptorsWithMethodSelector:methodSelectorcallback:^(id<GULApplicationDelegate> interceptor) {returnedValue |= [interceptor application:applicationopenURL:urloptions:options];}];#pragma clang diagnostic popif (openURLOptionsIMP) {returnedValue |= openURLOptionsIMP(self, methodSelector, application, url, options);}return returnedValue;}#endif // TARGET_OS_IOS || TARGET_OS_TV#if TARGET_OS_IOS- (BOOL)application:(GULApplication *)applicationopenURL:(NSURL *)urlsourceApplication:(NSString *)sourceApplicationannotation:(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;[GULAppDelegateSwizzlernotifyInterceptorsWithMethodSelector:methodSelectorcallback:^(id<GULApplicationDelegate> interceptor) {#pragma clang diagnostic push#pragma clang diagnostic ignored "-Wdeprecated-declarations"returnedValue |= [interceptor application:applicationopenURL:urlsourceApplication:sourceApplicationannotation: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 *)applicationhandleEventsForBackgroundURLSession:(NSString *)identifiercompletionHandler:(void (^)())completionHandler API_AVAILABLE(ios(7.0)) {#pragma clang diagnostic popSEL methodSelector = @selector(application:handleEventsForBackgroundURLSession:completionHandler:);NSValue *handleBackgroundSessionPointer =[GULAppDelegateSwizzler originalImplementationForSelector:methodSelector object:self];GULRealHandleEventsForBackgroundURLSessionIMP handleBackgroundSessionIMP =[handleBackgroundSessionPointer pointerValue];// Notify interceptors.[GULAppDelegateSwizzlernotifyInterceptorsWithMethodSelector:methodSelectorcallback:^(id<GULApplicationDelegate> interceptor) {[interceptor application:applicationhandleEventsForBackgroundURLSession:identifiercompletionHandler: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- (BOOL)application:(GULApplication *)applicationcontinueUserActivity:(NSUserActivity *)userActivityrestorationHandler:(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;#if !TARGET_OS_WATCH[GULAppDelegateSwizzlernotifyInterceptorsWithMethodSelector:methodSelectorcallback:^(id<GULApplicationDelegate> interceptor) {returnedValue |= [interceptor application:applicationcontinueUserActivity:userActivityrestorationHandler:restorationHandler];}];#endif// Call the real implementation if the real App Delegate has any.if (continueUserActivityIMP) {returnedValue |= continueUserActivityIMP(self, methodSelector, application, userActivity,restorationHandler);}return returnedValue;}#pragma mark - [Donor Methods] Remote Notifications- (void)application:(GULApplication *)applicationdonor_didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {SEL methodSelector = NSSelectorFromString(kGULDidRegisterForRemoteNotificationsSEL);NSValue *didRegisterForRemoteNotificationsIMPPointer =[GULAppDelegateSwizzler originalImplementationForSelector:methodSelector object:self];GULRealDidRegisterForRemoteNotificationsIMP didRegisterForRemoteNotificationsIMP =[didRegisterForRemoteNotificationsIMPPointer pointerValue];// Notify interceptors.[GULAppDelegateSwizzlernotifyInterceptorsWithMethodSelector:methodSelectorcallback:^(id<GULApplicationDelegate> interceptor) {NSInvocation *invocation = [GULAppDelegateSwizzlerappDelegateInvocationForSelector: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 *)applicationdonor_didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {SEL methodSelector = NSSelectorFromString(kGULDidFailToRegisterForRemoteNotificationsSEL);NSValue *didFailToRegisterForRemoteNotificationsIMPPointer =[GULAppDelegateSwizzler originalImplementationForSelector:methodSelector object:self];GULRealDidFailToRegisterForRemoteNotificationsIMP didFailToRegisterForRemoteNotificationsIMP =[didFailToRegisterForRemoteNotificationsIMPPointer pointerValue];// Notify interceptors.[GULAppDelegateSwizzlernotifyInterceptorsWithMethodSelector:methodSelectorcallback:^(id<GULApplicationDelegate> interceptor) {NSInvocation *invocation = [GULAppDelegateSwizzlerappDelegateInvocationForSelector: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 !TARGET_OS_WATCH && !TARGET_OS_OSX- (void)application:(GULApplication *)applicationdonor_didReceiveRemoteNotification:(NSDictionary *)userInfofetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {SEL methodSelector = NSSelectorFromString(kGULDidReceiveRemoteNotificationWithCompletionSEL);NSValue *didReceiveRemoteNotificationWithCompletionIMPPointer =[GULAppDelegateSwizzler originalImplementationForSelector:methodSelector object:self];GULRealDidReceiveRemoteNotificationWithCompletionIMPdidReceiveRemoteNotificationWithCompletionIMP =[didReceiveRemoteNotificationWithCompletionIMPPointer pointerValue];dispatch_group_t __block callbackGroup = dispatch_group_create();NSMutableArray<NSNumber *> *__block fetchResults = [NSMutableArray array];void (^localCompletionHandler)(UIBackgroundFetchResult) =^void(UIBackgroundFetchResult fetchResult) {[fetchResults addObject:[NSNumber numberWithInt:(int)fetchResult]];dispatch_group_leave(callbackGroup);};// Notify interceptors.[GULAppDelegateSwizzlernotifyInterceptorsWithMethodSelector:methodSelectorcallback:^(id<GULApplicationDelegate> interceptor) {dispatch_group_enter(callbackGroup);NSInvocation *invocation = [GULAppDelegateSwizzlerappDelegateInvocationForSelector:methodSelector];[invocation setTarget:interceptor];[invocation setSelector:methodSelector];[invocation setArgument:(void *)(&application) atIndex:2];[invocation setArgument:(void *)(&userInfo) atIndex:3];[invocation setArgument:(void *)(&localCompletionHandler)atIndex:4];[invocation invoke];}];// Call the real implementation if the real App Delegate has any.if (didReceiveRemoteNotificationWithCompletionIMP) {dispatch_group_enter(callbackGroup);didReceiveRemoteNotificationWithCompletionIMP(self, methodSelector, application, userInfo,localCompletionHandler);}dispatch_group_notify(callbackGroup, dispatch_get_main_queue(), ^() {BOOL allFetchesFailed = YES;BOOL anyFetchHasNewData = NO;for (NSNumber *oneResult in fetchResults) {UIBackgroundFetchResult result = oneResult.intValue;switch (result) {case UIBackgroundFetchResultNoData:allFetchesFailed = NO;break;case UIBackgroundFetchResultNewData:allFetchesFailed = NO;anyFetchHasNewData = YES;break;case UIBackgroundFetchResultFailed:break;}}UIBackgroundFetchResult finalFetchResult = UIBackgroundFetchResultNoData;if (allFetchesFailed) {finalFetchResult = UIBackgroundFetchResultFailed;} else if (anyFetchHasNewData) {finalFetchResult = UIBackgroundFetchResultNewData;} else {finalFetchResult = UIBackgroundFetchResultNoData;}completionHandler(finalFetchResult);});}#endif // !TARGET_OS_WATCH && !TARGET_OS_OSX- (void)application:(GULApplication *)applicationdonor_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"[GULAppDelegateSwizzlernotifyInterceptorsWithMethodSelector:methodSelectorcallback:^(id<GULApplicationDelegate> interceptor) {NSInvocation *invocation = [GULAppDelegateSwizzlerappDelegateInvocationForSelector: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,[NSStringstringWithFormat:@"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