|
|
@@ -1,1070 +0,0 @@
|
|
|
-// 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 pop
|
|
|
-
|
|
|
-typedef 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_OSX
|
|
|
-typedef void (*GULRealDidReceiveRemoteNotificationWithCompletionIMP)(
|
|
|
- id, SEL, GULApplication *, NSDictionary *, void (^)(UIBackgroundFetchResult));
|
|
|
-#endif // !TARGET_OS_WATCH && !TARGET_OS_OSX
|
|
|
-
|
|
|
-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 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: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 !TARGET_OS_WATCH && !TARGET_OS_OSX
|
|
|
- SEL didReceiveRemoteNotificationWithCompletionSEL =
|
|
|
- NSSelectorFromString(kGULDidReceiveRemoteNotificationWithCompletionSEL);
|
|
|
- SEL didReceiveRemoteNotificationWithCompletionDonorSEL =
|
|
|
- @selector(application:donor_didReceiveRemoteNotification:fetchCompletionHandler:);
|
|
|
- if ([appDelegate respondsToSelector:didReceiveRemoteNotificationWithCompletionSEL]) {
|
|
|
- // Only add the application:didReceiveRemoteNotification:fetchCompletionHandler: method if
|
|
|
- // the original AppDelegate implements it.
|
|
|
- // This fixes a bug if an app only implements application:didReceiveRemoteNotification:
|
|
|
- // (if we add the method with completion, iOS sees that one exists and does not call
|
|
|
- // the method without the completion, which in this case is the only one the app implements).
|
|
|
-
|
|
|
- [self proxyDestinationSelector:didReceiveRemoteNotificationWithCompletionSEL
|
|
|
- implementationsFromSourceSelector:didReceiveRemoteNotificationWithCompletionDonorSEL
|
|
|
- fromClass:[GULAppDelegateSwizzler class]
|
|
|
- toClass:appDelegateSubClass
|
|
|
- realClass:realClass
|
|
|
- storeDestinationImplementationTo:realImplementationsBySelector];
|
|
|
- }
|
|
|
-#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_WATCH
|
|
|
- id<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)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
|
|
|
-
|
|
|
-- (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;
|
|
|
-#if !TARGET_OS_WATCH
|
|
|
- [GULAppDelegateSwizzler
|
|
|
- notifyInterceptorsWithMethodSelector:methodSelector
|
|
|
- callback:^(id<GULApplicationDelegate> interceptor) {
|
|
|
- returnedValue |= [interceptor application:application
|
|
|
- continueUserActivity:userActivity
|
|
|
- restorationHandler: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 *)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 !TARGET_OS_WATCH && !TARGET_OS_OSX
|
|
|
-- (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];
|
|
|
-
|
|
|
- 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.
|
|
|
- [GULAppDelegateSwizzler
|
|
|
- notifyInterceptorsWithMethodSelector:methodSelector
|
|
|
- callback:^(id<GULApplicationDelegate> interceptor) {
|
|
|
- dispatch_group_enter(callbackGroup);
|
|
|
-
|
|
|
- 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 *)(&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 *)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
|