From b2eabf2954b89ab5cd2c6e2a5c467c8bc224e565 Mon Sep 17 00:00:00 2001 From: Maksym Malyhin Date: Tue, 26 Mar 2019 17:28:38 -0400 Subject: [PATCH 01/24] FIRAuthAppDelegateProxy: implementation updated to use GULAppDelegateSwizzler [WIP] --- .../Auth/Tests/FIRAuthAppDelegateProxyTests.m | 84 ++--- Firebase/Auth/Source/FIRAuth.m | 9 +- .../Auth/Source/FIRAuthAppDelegateProxy.h | 15 +- .../Auth/Source/FIRAuthAppDelegateProxy.m | 334 ++---------------- FirebaseAuth.podspec | 1 + 5 files changed, 70 insertions(+), 373 deletions(-) diff --git a/Example/Auth/Tests/FIRAuthAppDelegateProxyTests.m b/Example/Auth/Tests/FIRAuthAppDelegateProxyTests.m index 92871c51e52..e67ad985467 100644 --- a/Example/Auth/Tests/FIRAuthAppDelegateProxyTests.m +++ b/Example/Auth/Tests/FIRAuthAppDelegateProxyTests.m @@ -20,10 +20,16 @@ #import #import "FIRAuthAppDelegateProxy.h" +#import + #import NS_ASSUME_NONNULL_BEGIN +@interface GULAppDelegateSwizzler (FIRAuthAppDelegateProxyTests) ++ (void)proxyAppDelegate:(id)appDelegate; +@end + /** @class FIRAuthEmptyAppDelegate @brief A @c UIApplicationDelegate implementation that does nothing. */ @@ -208,56 +214,6 @@ - (void)testSharedInstance { XCTAssertEqual(proxy1, proxy2); } -/** @fn testNilApplication - @brief Tests that initialization fails if the application is nil. - */ -- (void)testNilApplication { - XCTAssertNil([[FIRAuthAppDelegateProxy alloc] initWithApplication:nil]); -} - -/** @fn testNilDelegate - @brief Tests that initialization fails if the application's delegate is nil. - */ -- (void)testNilDelegate { - OCMExpect([_mockApplication delegate]).andReturn(nil); - XCTAssertNil([[FIRAuthAppDelegateProxy alloc] initWithApplication:_mockApplication]); -} - -/** @fn testNonconformingDelegate - @brief Tests that initialization fails if the application's delegate does not conform to - @c UIApplicationDelegate protocol. - */ -- (void)testNonconformingDelegate { - OCMExpect([_mockApplication delegate]).andReturn(@"abc"); - XCTAssertNil([[FIRAuthAppDelegateProxy alloc] initWithApplication:_mockApplication]); -} - -/** @fn testDisabledByBundleEntry - @brief Tests that initialization fails if the proxy is disabled by a bundle entry. - */ -- (void)testDisabledByBundleEntry { - // Swizzle NSBundle's objectForInfoDictionaryKey to return @NO for the specific key. - Method method = class_getInstanceMethod([NSBundle class], @selector(objectForInfoDictionaryKey:)); - __block IMP originalImplementation; - IMP newImplmentation = imp_implementationWithBlock(^id(id object, NSString *key) { - if ([key isEqualToString:@"FirebaseAppDelegateProxyEnabled"]) { - return @NO; - } - typedef id (*Implementation)(id object, SEL cmd, NSString *key); - return ((Implementation)originalImplementation)(object, @selector(objectForInfoDictionaryKey:), - key); - }); - originalImplementation = method_setImplementation(method, newImplmentation); - - // Verify that initialization fails. - FIRAuthEmptyAppDelegate *delegate = [[FIRAuthEmptyAppDelegate alloc] init]; - OCMStub([_mockApplication delegate]).andReturn(delegate); - XCTAssertNil([[FIRAuthAppDelegateProxy alloc] initWithApplication:_mockApplication]); - - // Unswizzle. - imp_removeBlock(method_setImplementation(method, originalImplementation)); -} - // Deprecated methods are call intentionally in tests to verify behaviors on older iOS systems. #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" @@ -268,10 +224,14 @@ - (void)testDisabledByBundleEntry { - (void)testEmptyDelegateOneHandler { FIRAuthEmptyAppDelegate *delegate = [[FIRAuthEmptyAppDelegate alloc] init]; OCMExpect([_mockApplication delegate]).andReturn(delegate); + + [GULAppDelegateSwizzler proxyAppDelegate:delegate]; + __weak id weakProxy; @autoreleasepool { - FIRAuthAppDelegateProxy *proxy = - [[FIRAuthAppDelegateProxy alloc] initWithApplication:_mockApplication]; + FIRAuthAppDelegateProxy *proxy = [[FIRAuthAppDelegateProxy alloc] init]; + [GULAppDelegateSwizzler registerAppDelegateInterceptor:proxy]; + XCTAssertNotNil(proxy); // Verify certain methods are swizzled while others are not. @@ -391,10 +351,13 @@ - (void)testEmptyDelegateOneHandler { - (void)testLegacyDelegateTwoHandlers { FIRAuthLegacyAppDelegate *delegate = [[FIRAuthLegacyAppDelegate alloc] init]; OCMExpect([_mockApplication delegate]).andReturn(delegate); + + [GULAppDelegateSwizzler proxyAppDelegate:delegate]; + __weak id weakProxy; @autoreleasepool { - FIRAuthAppDelegateProxy *proxy = - [[FIRAuthAppDelegateProxy alloc] initWithApplication:_mockApplication]; + FIRAuthAppDelegateProxy *proxy = [[FIRAuthAppDelegateProxy alloc] init]; + [GULAppDelegateSwizzler registerAppDelegateInterceptor:proxy]; XCTAssertNotNil(proxy); // Verify certain methods are swizzled while others are not. @@ -539,10 +502,13 @@ - (void)testModernDelegateWithUnaffectedInstance { FIRAuthModernAppDelegate *delegate = [[FIRAuthModernAppDelegate alloc] init]; OCMExpect([_mockApplication delegate]).andReturn(delegate); FIRAuthModernAppDelegate *unaffectedDelegate = [[FIRAuthModernAppDelegate alloc] init]; + + [GULAppDelegateSwizzler proxyAppDelegate:delegate]; + __weak id weakProxy; @autoreleasepool { - FIRAuthAppDelegateProxy *proxy = - [[FIRAuthAppDelegateProxy alloc] initWithApplication:_mockApplication]; + FIRAuthAppDelegateProxy *proxy = [[FIRAuthAppDelegateProxy alloc] init]; + [GULAppDelegateSwizzler registerAppDelegateInterceptor:proxy]; XCTAssertNotNil(proxy); // Verify certain methods are swizzled while others are not. @@ -703,10 +669,12 @@ - (void)testModernDelegateWithUnaffectedInstance { - (void)testOtherLegacyDelegateHandleOpenURL { FIRAuthOtherLegacyAppDelegate *delegate = [[FIRAuthOtherLegacyAppDelegate alloc] init]; OCMExpect([_mockApplication delegate]).andReturn(delegate); + [GULAppDelegateSwizzler proxyAppDelegate:delegate]; + __weak id weakProxy; @autoreleasepool { - FIRAuthAppDelegateProxy *proxy = - [[FIRAuthAppDelegateProxy alloc] initWithApplication:_mockApplication]; + FIRAuthAppDelegateProxy *proxy = [[FIRAuthAppDelegateProxy alloc] init]; + [GULAppDelegateSwizzler registerAppDelegateInterceptor:proxy]; XCTAssertNotNil(proxy); // Verify certain methods are swizzled while others are not. diff --git a/Firebase/Auth/Source/FIRAuth.m b/Firebase/Auth/Source/FIRAuth.m index efb3a487dc6..abce76d2500 100644 --- a/Firebase/Auth/Source/FIRAuth.m +++ b/Firebase/Auth/Source/FIRAuth.m @@ -30,6 +30,7 @@ #import #import #import +#import #import "AuthProviders/EmailPassword/FIREmailPasswordAuthCredential.h" #import "FIRAdditionalUserInfo_Internal.h" @@ -373,8 +374,12 @@ - (nullable instancetype)initWithAPIKey:(NSString *)APIKey appName:(NSString *)a } UIApplication *application = [applicationClass sharedApplication]; - // Initialize the shared FIRAuthAppDelegateProxy instance in the main thread if not already. - [FIRAuthAppDelegateProxy sharedInstance]; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + [GULAppDelegateSwizzler proxyOriginalDelegate]; + [GULAppDelegateSwizzler registerAppDelegateInterceptor:[FIRAuthAppDelegateProxy sharedInstance]]; + }); + #endif // Continue with the rest of initialization in the work thread. diff --git a/Firebase/Auth/Source/FIRAuthAppDelegateProxy.h b/Firebase/Auth/Source/FIRAuthAppDelegateProxy.h index ccae93a7899..fcf1ae35f00 100644 --- a/Firebase/Auth/Source/FIRAuthAppDelegateProxy.h +++ b/Firebase/Auth/Source/FIRAuthAppDelegateProxy.h @@ -55,20 +55,7 @@ NS_ASSUME_NONNULL_BEGIN /** @class FIRAuthAppDelegateProxy @brief A manager for swizzling @c UIApplicationDelegate methods. */ -@interface FIRAuthAppDelegateProxy : NSObject - -/** @fn initWithApplication - @brief Initialize the instance with the given @c UIApplication. - @returns An initialized instance, or @c nil if a proxy cannot be established. - @remarks This method should only be called from tests if called outside of this class. - */ -- (nullable instancetype)initWithApplication:(nullable UIApplication *)application - NS_DESIGNATED_INITIALIZER; - -/** @fn init - @brief Call @c sharedInstance to get an instance of this class. - */ -- (instancetype)init NS_UNAVAILABLE; +@interface FIRAuthAppDelegateProxy : NSObject /** @fn addHandler: @brief Adds a handler for UIApplicationDelegate methods. diff --git a/Firebase/Auth/Source/FIRAuthAppDelegateProxy.m b/Firebase/Auth/Source/FIRAuthAppDelegateProxy.m index d97fedc6a69..f949f3da6de 100644 --- a/Firebase/Auth/Source/FIRAuthAppDelegateProxy.m +++ b/Firebase/Auth/Source/FIRAuthAppDelegateProxy.m @@ -16,7 +16,7 @@ #import "FIRAuthAppDelegateProxy.h" -#import +#import #import @@ -28,171 +28,22 @@ */ static NSString *const kProxyEnabledBundleKey = @"FirebaseAppDelegateProxyEnabled"; -/** @fn noop - @brief A function that does nothing. - @remarks This is used as the placeholder for unimplemented UApplicationDelegate methods, - because once we added a method there is no way to remove it from the class. - */ -#if !OBJC_OLD_DISPATCH_PROTOTYPES -static void noop(void) { -} -#else -static id noop(id object, SEL cmd, ...) { - return nil; -} -#endif - -/** @fn isIOS9orLater - @brief Checks whether the iOS version is 9 or later. - @returns Whether the iOS version is 9 or later. - */ -static BOOL isIOS9orLater() { -#if defined(__IPHONE_11_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_11_0) - if (@available(iOS 9.0, *)) { - return YES; - } - return NO; -#else - // UIApplicationOpenURLOptionsAnnotationKey is only available on iOS 9+. - return &UIApplicationOpenURLOptionsAnnotationKey != NULL; -#endif -} - @implementation FIRAuthAppDelegateProxy { - /** @var _appDelegate - @brief The application delegate whose method is being swizzled. - */ - id _appDelegate; - - /** @var _orginalImplementationsBySelector - @brief A map from selectors to original implementations that have been swizzled. - */ - NSMutableDictionary *_originalImplementationsBySelector; - /** @var _handlers @brief The array of weak pointers of `id`. */ NSPointerArray *_handlers; } -- (nullable instancetype)initWithApplication:(nullable UIApplication *)application { +- (instancetype)init +{ self = [super init]; if (self) { - id proxyEnabled = [[NSBundle mainBundle] objectForInfoDictionaryKey:kProxyEnabledBundleKey]; - if ([proxyEnabled isKindOfClass:[NSNumber class]] && !((NSNumber *)proxyEnabled).boolValue) { - return nil; - } - _appDelegate = application.delegate; - if (![_appDelegate conformsToProtocol:@protocol(UIApplicationDelegate)]) { - return nil; - } - _originalImplementationsBySelector = [[NSMutableDictionary alloc] init]; _handlers = [[NSPointerArray alloc] initWithOptions:NSPointerFunctionsWeakMemory]; - - // Swizzle the methods. - __weak FIRAuthAppDelegateProxy *weakSelf = self; - SEL registerDeviceTokenSelector = - @selector(application:didRegisterForRemoteNotificationsWithDeviceToken:); - [self replaceSelector:registerDeviceTokenSelector - withBlock:^(id object, UIApplication* application, NSData *deviceToken) { - [weakSelf object:object - selector:registerDeviceTokenSelector - application:application - didRegisterForRemoteNotificationsWithDeviceToken:deviceToken]; - }]; - SEL failToRegisterRemoteNotificationSelector = - @selector(application:didFailToRegisterForRemoteNotificationsWithError:); - [self replaceSelector:failToRegisterRemoteNotificationSelector - withBlock:^(id object, UIApplication* application, NSError *error) { - [weakSelf object:object - selector:failToRegisterRemoteNotificationSelector - application:application - didFailToRegisterForRemoteNotificationsWithError:error]; - }]; - SEL receiveNotificationSelector = @selector(application:didReceiveRemoteNotification:); - SEL receiveNotificationWithHandlerSelector = - @selector(application:didReceiveRemoteNotification:fetchCompletionHandler:); - if ([_appDelegate respondsToSelector:receiveNotificationWithHandlerSelector] || - ![_appDelegate respondsToSelector:receiveNotificationSelector]) { - // Replace the modern selector which is available on iOS 7 and above. - [self replaceSelector:receiveNotificationWithHandlerSelector - withBlock:^(id object, UIApplication *application, NSDictionary *notification, - void (^completionHandler)(UIBackgroundFetchResult)) { - [weakSelf object:object - selector:receiveNotificationWithHandlerSelector - application:application - didReceiveRemoteNotification:notification - fetchCompletionHandler:completionHandler]; - }]; - } else { - // Replace the deprecated selector because this is the only one that the client app uses. - [self replaceSelector:receiveNotificationSelector - withBlock:^(id object, UIApplication *application, NSDictionary *notification) { - [weakSelf object:object - selector:receiveNotificationSelector - application:application - didReceiveRemoteNotification:notification]; - }]; - } - SEL openURLOptionsSelector = @selector(application:openURL:options:); - SEL openURLAnnotationSelector = @selector(application:openURL:sourceApplication:annotation:); - SEL handleOpenURLSelector = @selector(application:handleOpenURL:); - if (isIOS9orLater() && - ([_appDelegate respondsToSelector:openURLOptionsSelector] || - (![_appDelegate respondsToSelector:openURLAnnotationSelector] && - ![_appDelegate respondsToSelector:handleOpenURLSelector]))) { - // Replace the modern selector which is avaliable on iOS 9 and above because this is the one - // that the client app uses or the client app doesn't use any of them. - [self replaceSelector:openURLOptionsSelector - withBlock:^BOOL(id object, UIApplication *application, NSURL *url, - NSDictionary *options) { - return [weakSelf object:object - selector:openURLOptionsSelector - application:application - openURL:url - options:options]; - }]; - } else if ([_appDelegate respondsToSelector:openURLAnnotationSelector] || - ![_appDelegate respondsToSelector:handleOpenURLSelector]) { - // Replace the longer form of the deprecated selectors on iOS 8 and below because this is the - // one that the client app uses or the client app doesn't use either of the applicable ones. - [self replaceSelector:openURLAnnotationSelector - withBlock:^(id object, UIApplication *application, NSURL *url, - NSString *sourceApplication, id annotation) { - return [weakSelf object:object - selector:openURLAnnotationSelector - application:application - openURL:url - sourceApplication:sourceApplication - annotation:annotation]; - }]; - } else { - // Replace the shorter form of the deprecated selectors on iOS 8 and below because this is - // the only one that the client app uses. - [self replaceSelector:handleOpenURLSelector - withBlock:^(id object, UIApplication *application, NSURL *url) { - return [weakSelf object:object - selector:handleOpenURLSelector - application:application - handleOpenURL:url]; - }]; - } - // Reset the application delegate to clear the system cache that indicates whether each of the - // openURL: methods is implemented on the application delegate. - application.delegate = nil; - application.delegate = _appDelegate; } return self; } -- (void)dealloc { - for (NSValue *selector in _originalImplementationsBySelector) { - IMP implementation = _originalImplementationsBySelector[selector].pointerValue; - Method method = class_getInstanceMethod([_appDelegate class], selector.pointerValue); - imp_removeBlock(method_setImplementation(method, implementation)); - } -} - - (void)addHandler:(__weak id)handler { @synchronized (_handlers) { [_handlers addPointer:(__bridge void *)handler]; @@ -202,143 +53,59 @@ - (void)addHandler:(__weak id)handler { + (nullable instancetype)sharedInstance { static dispatch_once_t onceToken; static FIRAuthAppDelegateProxy *_Nullable sharedInstance; - // iOS App extensions should not call [UIApplication sharedApplication], even if UIApplication - // responds to it. - static Class applicationClass = nil; dispatch_once(&onceToken, ^{ - if (![GULAppEnvironmentUtil isAppExtension]) { - Class cls = NSClassFromString(@"UIApplication"); - if (cls && [cls respondsToSelector:NSSelectorFromString(@"sharedApplication")]) { - applicationClass = cls; - } - } - UIApplication *application = [applicationClass sharedApplication]; - sharedInstance = [[self alloc] initWithApplication:application]; + sharedInstance = [[FIRAuthAppDelegateProxy alloc] init]; }); return sharedInstance; } #pragma mark - UIApplicationDelegate proxy methods. -- (void)object:(id)object - selector:(SEL)selector - application:(UIApplication *)application - didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { - if (object == _appDelegate) { - for (id handler in [self handlers]) { - [handler setAPNSToken:deviceToken]; - } - } - IMP originalImplementation = [self originalImplementationForSelector:selector]; - if (originalImplementation && originalImplementation != &noop) { - typedef void (*Implmentation)(id, SEL, UIApplication*, NSData *); - ((Implmentation)originalImplementation)(object, selector, application, deviceToken); - } -} - -- (void)object:(id)object - selector:(SEL)selector - application:(UIApplication *)application - didFailToRegisterForRemoteNotificationsWithError:(NSError *)error { - if (object == _appDelegate) { - for (id handler in [self handlers]) { - [handler handleAPNSTokenError:error]; - } - } - IMP originalImplementation = [self originalImplementationForSelector:selector]; - if (originalImplementation && originalImplementation != &noop) { - typedef void (*Implmentation)(id, SEL, UIApplication *, NSError *); - ((Implmentation)originalImplementation)(object, selector, application, error); +- (void)application:(UIApplication *)application +didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { + for (id handler in [self handlers]) { + [handler setAPNSToken:deviceToken]; } } -- (void)object:(id)object - selector:(SEL)selector - application:(UIApplication *)application - didReceiveRemoteNotification:(NSDictionary *)notification - fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { - if (object == _appDelegate) { - for (id handler in [self handlers]) { - if ([handler canHandleNotification:notification]) { - completionHandler(UIBackgroundFetchResultNoData); - return; - }; - } - } - IMP originalImplementation = [self originalImplementationForSelector:selector]; - if (originalImplementation && originalImplementation != &noop) { - typedef void (*Implmentation)(id, SEL, UIApplication*, NSDictionary *, - void (^)(UIBackgroundFetchResult)); - ((Implmentation)originalImplementation)(object, selector, application, notification, - completionHandler); +- (void)application:(UIApplication *)application +didFailToRegisterForRemoteNotificationsWithError:(NSError *)error { + for (id handler in [self handlers]) { + [handler handleAPNSTokenError:error]; } } -- (void)object:(id)object - selector:(SEL)selector - application:(UIApplication *)application - didReceiveRemoteNotification:(NSDictionary *)notification { - if (object == _appDelegate) { - for (id handler in [self handlers]) { - if ([handler canHandleNotification:notification]) { - return; - }; - } - } - IMP originalImplementation = [self originalImplementationForSelector:selector]; - if (originalImplementation && originalImplementation != &noop) { - typedef void (*Implmentation)(id, SEL, UIApplication*, NSDictionary *); - ((Implmentation)originalImplementation)(object, selector, application, notification); +- (void)application:(UIApplication *)application +didReceiveRemoteNotification:(NSDictionary *)userInfo +fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { + for (id handler in [self handlers]) { + if ([handler canHandleNotification:userInfo]) { + completionHandler(UIBackgroundFetchResultNoData); + return; + }; } } -- (BOOL)object:(id)object - selector:(SEL)selector - application:(UIApplication *)application - openURL:(NSURL *)url - options:(NSDictionary *)options { - if (object == _appDelegate && [self delegateCanHandleURL:url]) { - return YES; - } - IMP originalImplementation = [self originalImplementationForSelector:selector]; - if (originalImplementation && originalImplementation != &noop) { - typedef BOOL (*Implmentation)(id, SEL, UIApplication*, NSURL *, NSDictionary *); - return ((Implmentation)originalImplementation)(object, selector, application, url, options); +- (void)application:(UIApplication *)application +didReceiveRemoteNotification:(NSDictionary *)userInfo { + for (id handler in [self handlers]) { + if ([handler canHandleNotification:userInfo]) { + return; + }; } - return NO; } -- (BOOL)object:(id)object - selector:(SEL)selector - application:(UIApplication *)application - openURL:(NSURL *)url - sourceApplication:(NSString *)sourceApplication - annotation:(id)annotation { - if (object == _appDelegate && [self delegateCanHandleURL:url]) { - return YES; - } - IMP originalImplementation = [self originalImplementationForSelector:selector]; - if (originalImplementation && originalImplementation != &noop) { - typedef BOOL (*Implmentation)(id, SEL, UIApplication*, NSURL *, NSString *, id); - return ((Implmentation)originalImplementation)(object, selector, application, url, - sourceApplication, annotation); - } - return NO; +- (BOOL)application:(UIApplication *)app + openURL:(NSURL *)url + options:(NSDictionary *)options { + return [self delegateCanHandleURL:url]; } -- (BOOL)object:(id)object - selector:(SEL)selector - application:(UIApplication *)application - handleOpenURL:(NSURL *)url { - if (object == _appDelegate && [self delegateCanHandleURL:url]) { - return YES; - } - IMP originalImplementation = [self originalImplementationForSelector:selector]; - if (originalImplementation && originalImplementation != &noop) { - typedef BOOL (*Implmentation)(id, SEL, UIApplication*, NSURL *); - return ((Implmentation)originalImplementation)(object, selector, application, url); - } - return NO; +- (BOOL)application:(UIApplication *)application + openURL:(NSURL *)url + sourceApplication:(nullable NSString *)sourceApplication + annotation:(id)annotation { + return [self delegateCanHandleURL:url]; } #pragma mark - Internal Methods @@ -376,37 +143,6 @@ - (BOOL)delegateCanHandleURL:(NSURL *)url { } } -/** @fn replaceSelector:withBlock: - @brief replaces the implementation for a method of `_appDelegate` specified by a selector. - @param selector The selector for the method. - @param block The block as the new implementation of the method. - */ -- (void)replaceSelector:(SEL)selector withBlock:(id)block { - Method originalMethod = class_getInstanceMethod([_appDelegate class], selector); - IMP newImplementation = imp_implementationWithBlock(block); - IMP originalImplementation; - if (originalMethod) { - originalImplementation = method_setImplementation(originalMethod, newImplementation) ?: &noop; - } else { - // The original method was not implemented in the class, add it with the new implementation. - struct objc_method_description methodDescription = - protocol_getMethodDescription(@protocol(UIApplicationDelegate), selector, NO, YES); - class_addMethod([_appDelegate class], selector, newImplementation, methodDescription.types); - originalImplementation = &noop; - } - _originalImplementationsBySelector[[NSValue valueWithPointer:selector]] = - [NSValue valueWithPointer:originalImplementation]; -} - -/** @fn originalImplementationForSelector: - @brief Gets the original implementation for the given selector. - @param selector The selector for the method that has been replaced. - @return The original implementation if there was one. - */ -- (IMP)originalImplementationForSelector:(SEL)selector { - return _originalImplementationsBySelector[[NSValue valueWithPointer:selector]].pointerValue; -} - @end NS_ASSUME_NONNULL_END diff --git a/FirebaseAuth.podspec b/FirebaseAuth.podspec index 9dd85a9c168..bd5a4f44b3b 100644 --- a/FirebaseAuth.podspec +++ b/FirebaseAuth.podspec @@ -64,6 +64,7 @@ supports email and password accounts, as well as several 3rd party authenticatio s.ios.framework = 'SafariServices' s.dependency 'FirebaseAuthInterop', '~> 1.0' s.dependency 'FirebaseCore', '~> 5.2' + s.dependency 'GoogleUtilities/AppDelegateSwizzler', '~> 5.2' s.dependency 'GoogleUtilities/Environment', '~> 5.2' s.dependency 'GTMSessionFetcher/Core', '~> 1.1' end From 4ab86def21669dbd18ebd0f5cec79ac7e425ee34 Mon Sep 17 00:00:00 2001 From: Maksym Malyhin Date: Wed, 27 Mar 2019 12:49:32 -0400 Subject: [PATCH 02/24] GULAppDelegateSwizzler - make sure subclass name is unique. --- GoogleUtilities/AppDelegateSwizzler/GULAppDelegateSwizzler.m | 3 +-- .../Example/GoogleUtilities.xcodeproj/project.pbxproj | 2 ++ .../Example/Tests/Swizzler/GULAppDelegateSwizzlerTest.m | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/GoogleUtilities/AppDelegateSwizzler/GULAppDelegateSwizzler.m b/GoogleUtilities/AppDelegateSwizzler/GULAppDelegateSwizzler.m index bb71991b0e7..8bbabad3778 100644 --- a/GoogleUtilities/AppDelegateSwizzler/GULAppDelegateSwizzler.m +++ b/GoogleUtilities/AppDelegateSwizzler/GULAppDelegateSwizzler.m @@ -277,9 +277,8 @@ + (void)createSubclassWithObject:(id)anObject { // Create GUL__ NSString *classNameWithPrefix = [kGULAppDelegatePrefix stringByAppendingString:NSStringFromClass(realClass)]; - NSTimeInterval timestamp = [NSDate date].timeIntervalSince1970; NSString *newClassName = - [NSString stringWithFormat:@"%@-%0.0f", classNameWithPrefix, timestamp * 1000]; + [NSString stringWithFormat:@"%@-%@", classNameWithPrefix, [NSUUID UUID].UUIDString]; if (NSClassFromString(newClassName)) { GULLogError(kGULLoggerSwizzler, NO, diff --git a/GoogleUtilities/Example/GoogleUtilities.xcodeproj/project.pbxproj b/GoogleUtilities/Example/GoogleUtilities.xcodeproj/project.pbxproj index 754333d6676..08778f3f85c 100644 --- a/GoogleUtilities/Example/GoogleUtilities.xcodeproj/project.pbxproj +++ b/GoogleUtilities/Example/GoogleUtilities.xcodeproj/project.pbxproj @@ -13,6 +13,7 @@ 6003F5B0195388D20070C39A /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F5AF195388D20070C39A /* XCTest.framework */; }; 6003F5B1195388D20070C39A /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F58D195388D20070C39A /* Foundation.framework */; }; 6003F5B2195388D20070C39A /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F591195388D20070C39A /* UIKit.framework */; }; + 9A7C37C2224BD9C600033B0D /* GULAppDelegateSwizzlerTest.m in Sources */ = {isa = PBXBuildFile; fileRef = EFBE67F92101401100E756A7 /* GULAppDelegateSwizzlerTest.m */; }; DE5CF98E20F686310063FFDD /* GULAppEnvironmentUtilTest.m in Sources */ = {isa = PBXBuildFile; fileRef = DE5CF98C20F686290063FFDD /* GULAppEnvironmentUtilTest.m */; }; DE84BBC421D7EC900048A176 /* GULUserDefaultsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE84BBC321D7EC900048A176 /* GULUserDefaultsTests.m */; }; DE84BBC521D7EC900048A176 /* GULUserDefaultsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE84BBC321D7EC900048A176 /* GULUserDefaultsTests.m */; }; @@ -618,6 +619,7 @@ DEC977D920F68C3300014E20 /* GULNetworkTest.m in Sources */, EFBE67FA2101401100E756A7 /* GULSwizzlerTest.m in Sources */, EFBE67FD2101401100E756A7 /* GULObjectSwizzlerTest.m in Sources */, + 9A7C37C2224BD9C600033B0D /* GULAppDelegateSwizzlerTest.m in Sources */, EFBE67FE2101401100E756A7 /* GULRuntimeClassSnapshotTests.m in Sources */, EFBE67FC2101401100E756A7 /* GULRuntimeClassDiffTests.m in Sources */, DE5CF98E20F686310063FFDD /* GULAppEnvironmentUtilTest.m in Sources */, diff --git a/GoogleUtilities/Example/Tests/Swizzler/GULAppDelegateSwizzlerTest.m b/GoogleUtilities/Example/Tests/Swizzler/GULAppDelegateSwizzlerTest.m index 4eb1cbc9cdf..d2f8f1fbefe 100644 --- a/GoogleUtilities/Example/Tests/Swizzler/GULAppDelegateSwizzlerTest.m +++ b/GoogleUtilities/Example/Tests/Swizzler/GULAppDelegateSwizzlerTest.m @@ -199,6 +199,7 @@ @implementation GULAppDelegateSwizzlerTest - (void)tearDown { [GULAppDelegateSwizzler clearInterceptors]; + [super tearDown]; } /** Tests proxying an object that responds to UIApplicationDelegate protocol and makes sure that From 1f1560a8543322cb0c696362e6aa57e33651bd35 Mon Sep 17 00:00:00 2001 From: Maksym Malyhin Date: Wed, 27 Mar 2019 12:50:03 -0400 Subject: [PATCH 03/24] GoogleUtilities: enable code coverage --- .../xcshareddata/xcschemes/Example_iOS.xcscheme | 1 + 1 file changed, 1 insertion(+) diff --git a/GoogleUtilities/Example/GoogleUtilities.xcodeproj/xcshareddata/xcschemes/Example_iOS.xcscheme b/GoogleUtilities/Example/GoogleUtilities.xcodeproj/xcshareddata/xcschemes/Example_iOS.xcscheme index f4ef27750b9..31b6b579ff4 100644 --- a/GoogleUtilities/Example/GoogleUtilities.xcodeproj/xcshareddata/xcschemes/Example_iOS.xcscheme +++ b/GoogleUtilities/Example/GoogleUtilities.xcodeproj/xcshareddata/xcschemes/Example_iOS.xcscheme @@ -26,6 +26,7 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + codeCoverageEnabled = "YES" shouldUseLaunchSchemeArgsEnv = "YES"> Date: Wed, 27 Mar 2019 14:05:08 -0400 Subject: [PATCH 04/24] GULAppDelegateSwizzler: Add support of remote notification methods --- .../GULAppDelegateSwizzler.m | 177 ++++++++++++++++++ .../Swizzler/GULAppDelegateSwizzlerTest.m | 9 + 2 files changed, 186 insertions(+) diff --git a/GoogleUtilities/AppDelegateSwizzler/GULAppDelegateSwizzler.m b/GoogleUtilities/AppDelegateSwizzler/GULAppDelegateSwizzler.m index 8bbabad3778..8a55692029a 100644 --- a/GoogleUtilities/AppDelegateSwizzler/GULAppDelegateSwizzler.m +++ b/GoogleUtilities/AppDelegateSwizzler/GULAppDelegateSwizzler.m @@ -47,6 +47,18 @@ typedef BOOL (*GULRealContinueUserActivityIMP)( id, SEL, UIApplication *, NSUserActivity *, void (^)(NSArray *restorableObjects)); #pragma clang diagnostic pop +typedef void (*GULRealDidRegisterForRemoteNotificationsIMP)(id, SEL, UIApplication *, NSData *); + +typedef void (*GULRealDidFailToRegisterForRemoteNotificationsIMP)(id, + SEL, + UIApplication *, + NSError *); + +typedef void (*GULRealDidReceiveRemoteNotificationIMP)(id, SEL, UIApplication *, NSDictionary *); + +typedef void (*GULRealDidReceiveRemoteNotificationWithCompletionIMP)( + id, SEL, UIApplication *, NSDictionary *, void (^)(UIBackgroundFetchResult)); + typedef void (^GULAppDelegateInterceptorCallback)(id); // The strings below are the keys for associated objects. @@ -55,6 +67,15 @@ typedef BOOL (*GULRealContinueUserActivityIMP)( static char const *const kGULOpenURLOptionsIMPKey = "GUL_openURLOptionsIMP"; static char const *const kGULOpenURLOptionsSourceAnnotationsIMPKey = "GUL_openURLSourceApplicationAnnotationIMP"; +static char const *const kGULRealDidRegisterForRemoteNotificationsIMPKey = + "GUL_didRegisterForRemoteNotificationsIMP"; +static char const *const kGULRealDidFailToRegisterForRemoteNotificationsIMPKey = + "GUL_didFailToRegisterForRemoteNotificationsIMP"; +static char const *const kGULRealDidReceiveRemoteNotificationIMPKey = + "GUL_didReceiveRemoteNotificationIMP"; +static char const *const kGULRealDidReceiveRemoteNotificationWithCompletionIMPKey = + "GUL_didReceiveRemoteNotificationWithCompletionIMP"; + static char const *const kGULRealClassKey = "GUL_realClass"; static NSString *const kGULAppDelegateKeyPath = @"delegate"; @@ -359,6 +380,59 @@ + (void)createSubclassWithObject:(id)anObject { NSValue *handleBackgroundSessionIMPPointer = [NSValue valueWithPointer:handleBackgroundSessionIMP]; + // For application:didRegisterForRemoteNotificationsWithDeviceToken: + SEL didRegisterForRemoteNotificationsSEL = @selector(application: + didRegisterForRemoteNotificationsWithDeviceToken:); + [GULAppDelegateSwizzler addInstanceMethodWithSelector:didRegisterForRemoteNotificationsSEL + fromClass:[GULAppDelegateSwizzler class] + toClass:appDelegateSubClass]; + GULRealDidRegisterForRemoteNotificationsIMP didRegisterForRemoteNotificationsIMP = + (GULRealDidRegisterForRemoteNotificationsIMP)[GULAppDelegateSwizzler + implementationOfMethodSelector:didRegisterForRemoteNotificationsSEL + fromClass:realClass]; + NSValue *didRegisterForRemoteNotificationsIMPPointer = + [NSValue valueWithPointer:didRegisterForRemoteNotificationsIMP]; + + // For application:didFailToRegisterForRemoteNotificationsWithError: + SEL didFailToRegisterForRemoteNotificationsSEL = @selector(application: + didFailToRegisterForRemoteNotificationsWithError:); + [GULAppDelegateSwizzler addInstanceMethodWithSelector:didFailToRegisterForRemoteNotificationsSEL + fromClass:[GULAppDelegateSwizzler class] + toClass:appDelegateSubClass]; + GULRealDidFailToRegisterForRemoteNotificationsIMP didFailToRegisterForRemoteNotificationsIMP = + (GULRealDidFailToRegisterForRemoteNotificationsIMP)[GULAppDelegateSwizzler + implementationOfMethodSelector:didFailToRegisterForRemoteNotificationsSEL + fromClass:realClass]; + NSValue *didFailToRegisterForRemoteNotificationsIMPPointer = + [NSValue valueWithPointer:didFailToRegisterForRemoteNotificationsIMP]; + + // For application:didReceiveRemoteNotification:fetchCompletionHandler: + SEL didReceiveRemoteNotificationWithCompletionSEL = + @selector(application:didReceiveRemoteNotification:fetchCompletionHandler:); + [GULAppDelegateSwizzler + addInstanceMethodWithSelector:didReceiveRemoteNotificationWithCompletionSEL + fromClass:[GULAppDelegateSwizzler class] + toClass:appDelegateSubClass]; + GULRealDidReceiveRemoteNotificationWithCompletionIMP + didReceiveRemoteNotificationWithCompletionIMP = + (GULRealDidReceiveRemoteNotificationWithCompletionIMP)[GULAppDelegateSwizzler + implementationOfMethodSelector:didReceiveRemoteNotificationWithCompletionSEL + fromClass:realClass]; + NSValue *didReceiveRemoteNotificationWithCompletionIMPPointer = + [NSValue valueWithPointer:didReceiveRemoteNotificationWithCompletionIMP]; + + // For application:didReceiveRemoteNotification: + SEL didReceiveRemoteNotificationSEL = @selector(application:didReceiveRemoteNotification:); + [GULAppDelegateSwizzler addInstanceMethodWithSelector:didReceiveRemoteNotificationSEL + fromClass:[GULAppDelegateSwizzler class] + toClass:appDelegateSubClass]; + GULRealDidReceiveRemoteNotificationIMP didReceiveRemoteNotificationIMP = + (GULRealDidReceiveRemoteNotificationIMP) + [GULAppDelegateSwizzler implementationOfMethodSelector:didReceiveRemoteNotificationSEL + fromClass:realClass]; + NSValue *didReceiveRemoteNotificationIMPPointer = + [NSValue valueWithPointer:didReceiveRemoteNotificationIMP]; + // Override the description too so the custom class name will not show up. [GULAppDelegateSwizzler addInstanceMethodWithDestinationSelector:@selector(description) withImplementationFromSourceSelector:@selector(fakeDescription) @@ -376,6 +450,18 @@ + (void)createSubclassWithObject:(id)anObject { } objc_setAssociatedObject(anObject, &kGULOpenURLOptionsSourceAnnotationsIMPKey, openURLSourceAppAnnotationIMPPointer, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + objc_setAssociatedObject(anObject, &kGULRealDidRegisterForRemoteNotificationsIMPKey, + didRegisterForRemoteNotificationsIMPPointer, + OBJC_ASSOCIATION_RETAIN_NONATOMIC); + objc_setAssociatedObject(anObject, &kGULRealDidFailToRegisterForRemoteNotificationsIMPKey, + didFailToRegisterForRemoteNotificationsIMPPointer, + OBJC_ASSOCIATION_RETAIN_NONATOMIC); + objc_setAssociatedObject(anObject, &kGULRealDidReceiveRemoteNotificationWithCompletionIMPKey, + didReceiveRemoteNotificationWithCompletionIMPPointer, + OBJC_ASSOCIATION_RETAIN_NONATOMIC); + objc_setAssociatedObject(anObject, &kGULRealDidReceiveRemoteNotificationIMPKey, + didReceiveRemoteNotificationIMPPointer, + OBJC_ASSOCIATION_RETAIN_NONATOMIC); objc_setAssociatedObject(anObject, &kGULRealClassKey, realClass, OBJC_ASSOCIATION_RETAIN_NONATOMIC); @@ -647,6 +733,97 @@ - (BOOL)application:(UIApplication *)application } #pragma clang diagnostic pop +#pragma mark - [Donor Methods] Remote Notifications + +- (void)application:(UIApplication *)application + didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { + NSValue *didRegisterForRemoteNotificationsIMPPointer = + objc_getAssociatedObject(self, &kGULRealDidRegisterForRemoteNotificationsIMPKey); + GULRealDidRegisterForRemoteNotificationsIMP didRegisterForRemoteNotificationsIMP = + [didRegisterForRemoteNotificationsIMPPointer pointerValue]; + + // Notify interceptors. + SEL methodSelector = @selector(application:didRegisterForRemoteNotificationsWithDeviceToken:); + [GULAppDelegateSwizzler + notifyInterceptorsWithMethodSelector:methodSelector + callback:^(id interceptor) { + [interceptor application:application + didRegisterForRemoteNotificationsWithDeviceToken: + deviceToken]; + }]; + // Call the real implementation if the real App Delegate has any. + if (didRegisterForRemoteNotificationsIMP) { + didRegisterForRemoteNotificationsIMP(self, methodSelector, application, deviceToken); + } +} + +- (void)application:(UIApplication *)application + didFailToRegisterForRemoteNotificationsWithError:(NSError *)error { + NSValue *didFailToRegisterForRemoteNotificationsIMPPointer = + objc_getAssociatedObject(self, &kGULRealDidFailToRegisterForRemoteNotificationsIMPKey); + GULRealDidFailToRegisterForRemoteNotificationsIMP didFailToRegisterForRemoteNotificationsIMP = + [didFailToRegisterForRemoteNotificationsIMPPointer pointerValue]; + + // Notify interceptors. + SEL methodSelector = @selector(application:didFailToRegisterForRemoteNotificationsWithError:); + [GULAppDelegateSwizzler + notifyInterceptorsWithMethodSelector:methodSelector + callback:^(id interceptor) { + [interceptor application:application + didFailToRegisterForRemoteNotificationsWithError:error]; + }]; + // Call the real implementation if the real App Delegate has any. + if (didFailToRegisterForRemoteNotificationsIMP) { + didFailToRegisterForRemoteNotificationsIMP(self, methodSelector, application, error); + } +} + +- (void)application:(UIApplication *)application + didReceiveRemoteNotification:(NSDictionary *)userInfo + fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { + NSValue *didReceiveRemoteNotificationWithCompletionIMPPointer = + objc_getAssociatedObject(self, &kGULRealDidReceiveRemoteNotificationWithCompletionIMPKey); + GULRealDidReceiveRemoteNotificationWithCompletionIMP + didReceiveRemoteNotificationWithCompletionIMP = + [didReceiveRemoteNotificationWithCompletionIMPPointer pointerValue]; + + // Notify interceptors. + SEL methodSelector = @selector(application:didReceiveRemoteNotification:fetchCompletionHandler:); + [GULAppDelegateSwizzler + notifyInterceptorsWithMethodSelector:methodSelector + callback:^(id interceptor) { + [interceptor application:application + didReceiveRemoteNotification:userInfo + fetchCompletionHandler:completionHandler]; + }]; + // Call the real implementation if the real App Delegate has any. + if (didReceiveRemoteNotificationWithCompletionIMP) { + didReceiveRemoteNotificationWithCompletionIMP(self, methodSelector, application, userInfo, + completionHandler); + } +} + +- (void)application:(UIApplication *)application + didReceiveRemoteNotification:(NSDictionary *)userInfo { + NSValue *didReceiveRemoteNotificationIMPPointer = + objc_getAssociatedObject(self, &kGULRealDidReceiveRemoteNotificationIMPKey); + GULRealDidReceiveRemoteNotificationIMP didReceiveRemoteNotificationIMP = + [didReceiveRemoteNotificationIMPPointer pointerValue]; + + // Notify interceptors. + SEL methodSelector = @selector(application:didReceiveRemoteNotification:); + [GULAppDelegateSwizzler + notifyInterceptorsWithMethodSelector:methodSelector + callback:^(id interceptor) { + [interceptor application:application + didReceiveRemoteNotification:userInfo]; + }]; + // Call the real implementation if the real App Delegate has any. + if (didReceiveRemoteNotificationIMP) { + didReceiveRemoteNotificationIMP(self, methodSelector, application, userInfo); + } +} + + (void)proxyAppDelegate:(id)appDelegate { id originalDelegate = appDelegate; // Do not create a subclass if it is not enabled. diff --git a/GoogleUtilities/Example/Tests/Swizzler/GULAppDelegateSwizzlerTest.m b/GoogleUtilities/Example/Tests/Swizzler/GULAppDelegateSwizzlerTest.m index d2f8f1fbefe..744361aa9a0 100644 --- a/GoogleUtilities/Example/Tests/Swizzler/GULAppDelegateSwizzlerTest.m +++ b/GoogleUtilities/Example/Tests/Swizzler/GULAppDelegateSwizzlerTest.m @@ -243,6 +243,15 @@ - (void)testProxyAppDelegate { XCTAssertTrue([realAppDelegate respondsToSelector:@selector(application: handleEventsForBackgroundURLSession:completionHandler:)]); + XCTAssertTrue([realAppDelegate + respondsToSelector:@selector(application:didRegisterForRemoteNotificationsWithDeviceToken:)]); + XCTAssertTrue([realAppDelegate + respondsToSelector:@selector(application:didFailToRegisterForRemoteNotificationsWithError:)]); + XCTAssertTrue([realAppDelegate + respondsToSelector:@selector(application: + didReceiveRemoteNotification:fetchCompletionHandler:)]); + XCTAssertTrue([realAppDelegate respondsToSelector:@selector(application: + didReceiveRemoteNotification:)]); // Make sure that the class has changed. XCTAssertNotEqualObjects([realAppDelegate class], realAppDelegateClassBefore); From 27c8b5bf18d843d2440588084f16840ea3c03948 Mon Sep 17 00:00:00 2001 From: Maksym Malyhin Date: Wed, 27 Mar 2019 16:30:54 -0400 Subject: [PATCH 05/24] GULAppDelegateSwizzler - tests for remote notification methods --- .../GULAppDelegateSwizzler.m | 31 ++-- .../Swizzler/GULAppDelegateSwizzlerTest.m | 164 +++++++++++++++++- 2 files changed, 182 insertions(+), 13 deletions(-) diff --git a/GoogleUtilities/AppDelegateSwizzler/GULAppDelegateSwizzler.m b/GoogleUtilities/AppDelegateSwizzler/GULAppDelegateSwizzler.m index 8a55692029a..827f8c3f71d 100644 --- a/GoogleUtilities/AppDelegateSwizzler/GULAppDelegateSwizzler.m +++ b/GoogleUtilities/AppDelegateSwizzler/GULAppDelegateSwizzler.m @@ -407,19 +407,28 @@ + (void)createSubclassWithObject:(id)anObject { [NSValue valueWithPointer:didFailToRegisterForRemoteNotificationsIMP]; // For application:didReceiveRemoteNotification:fetchCompletionHandler: + NSValue *didReceiveRemoteNotificationWithCompletionIMPPointer; SEL didReceiveRemoteNotificationWithCompletionSEL = @selector(application:didReceiveRemoteNotification:fetchCompletionHandler:); - [GULAppDelegateSwizzler - addInstanceMethodWithSelector:didReceiveRemoteNotificationWithCompletionSEL - fromClass:[GULAppDelegateSwizzler class] - toClass:appDelegateSubClass]; - GULRealDidReceiveRemoteNotificationWithCompletionIMP - didReceiveRemoteNotificationWithCompletionIMP = - (GULRealDidReceiveRemoteNotificationWithCompletionIMP)[GULAppDelegateSwizzler - implementationOfMethodSelector:didReceiveRemoteNotificationWithCompletionSEL - fromClass:realClass]; - NSValue *didReceiveRemoteNotificationWithCompletionIMPPointer = - [NSValue valueWithPointer:didReceiveRemoteNotificationWithCompletionIMP]; + if ([anObject 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). + + [GULAppDelegateSwizzler + addInstanceMethodWithSelector:didReceiveRemoteNotificationWithCompletionSEL + fromClass:[GULAppDelegateSwizzler class] + toClass:appDelegateSubClass]; + GULRealDidReceiveRemoteNotificationWithCompletionIMP + didReceiveRemoteNotificationWithCompletionIMP = + (GULRealDidReceiveRemoteNotificationWithCompletionIMP)[GULAppDelegateSwizzler + implementationOfMethodSelector:didReceiveRemoteNotificationWithCompletionSEL + fromClass:realClass]; + didReceiveRemoteNotificationWithCompletionIMPPointer = + [NSValue valueWithPointer:didReceiveRemoteNotificationWithCompletionIMP]; + } // For application:didReceiveRemoteNotification: SEL didReceiveRemoteNotificationSEL = @selector(application:didReceiveRemoteNotification:); diff --git a/GoogleUtilities/Example/Tests/Swizzler/GULAppDelegateSwizzlerTest.m b/GoogleUtilities/Example/Tests/Swizzler/GULAppDelegateSwizzlerTest.m index 744361aa9a0..2ad47e4c786 100644 --- a/GoogleUtilities/Example/Tests/Swizzler/GULAppDelegateSwizzlerTest.m +++ b/GoogleUtilities/Example/Tests/Swizzler/GULAppDelegateSwizzlerTest.m @@ -60,7 +60,17 @@ @interface GULTestAppDelegate : UIResponder { /** A URL property that is set by the app delegate methods, which is then used to verify if the app * delegate methods were properly called. */ -@property(nonatomic, copy) NSString *url; +@property(nonatomic, copy) NSURL *url; + +@property(nonatomic, strong) NSData *remoteNotificationsDeviceToken; +@property(nonatomic, strong) NSError *failToRegisterForRemoteNotificationsError; +@property(nonatomic, strong) NSDictionary *remoteNotification; +@property(nonatomic, copy) void (^remoteNotificationCompletionHandler)(UIBackgroundFetchResult); + +/** + * The application is set each time a UIApplicationDelegate method is called + */ +@property(nonatomic, weak) UIApplication *application; @end @@ -120,7 +130,8 @@ - (instancetype)init { - (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary *)options { - _url = [url copy]; + self.application = app; + self.url = url; _isOpenURLOptionsMethodCalled = YES; return NO; } @@ -128,9 +139,36 @@ - (BOOL)application:(UIApplication *)app - (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(nonnull NSString *)identifier completionHandler:(nonnull void (^)(void))completionHandler { + self.application = application; _backgroundSessionID = [identifier copy]; } +- (void)application:(UIApplication *)application + didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { + self.application = application; + self.remoteNotificationsDeviceToken = deviceToken; +} + +- (void)application:(UIApplication *)application + didFailToRegisterForRemoteNotificationsWithError:(NSError *)error { + self.application = application; + self.failToRegisterForRemoteNotificationsError = error; +} + +- (void)application:(UIApplication *)application + didReceiveRemoteNotification:(NSDictionary *)userInfo { + self.application = application; + self.remoteNotification = userInfo; +} + +- (void)application:(UIApplication *)application + didReceiveRemoteNotification:(NSDictionary *)userInfo + fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { + self.application = application; + self.remoteNotification = userInfo; + self.remoteNotificationCompletionHandler = completionHandler; +} + // These are methods to test whether changing the class still maintains behavior that the app // delegate proxy shouldn't have modified. @@ -657,6 +695,128 @@ - (void)testApplicationContinueUserActivityRestorationHandlerResultsAreORed { XCTAssertTrue(shouldContinueUserActvitiy); } +- (void)testApplicationDidRegisterForRemoteNotificationsIsInvokedOnInterceptors { + NSData *deviceToken = [NSData data]; + UIApplication *application = [UIApplication sharedApplication]; + + id interceptor = OCMProtocolMock(@protocol(UIApplicationDelegate)); + OCMExpect([interceptor application:application + didRegisterForRemoteNotificationsWithDeviceToken:deviceToken]); + + id interceptor2 = OCMProtocolMock(@protocol(UIApplicationDelegate)); + OCMExpect([interceptor2 application:application + didRegisterForRemoteNotificationsWithDeviceToken:deviceToken]); + + GULTestAppDelegate *testAppDelegate = [[GULTestAppDelegate alloc] init]; + [GULAppDelegateSwizzler proxyAppDelegate:testAppDelegate]; + [GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor]; + [GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor2]; + + [testAppDelegate application:application + didRegisterForRemoteNotificationsWithDeviceToken:deviceToken]; + OCMVerifyAll(interceptor); + OCMVerifyAll(interceptor2); + + XCTAssertEqual(testAppDelegate.application, application); + XCTAssertEqual(testAppDelegate.remoteNotificationsDeviceToken, deviceToken); +} + +- (void)testApplicationDidFailToRegisterForRemoteNotificationsIsInvokedOnInterceptors { + NSError *error = [NSError errorWithDomain:@"test" code:-1 userInfo:nil]; + UIApplication *application = [UIApplication sharedApplication]; + + id interceptor = OCMProtocolMock(@protocol(UIApplicationDelegate)); + OCMExpect([interceptor application:application + didFailToRegisterForRemoteNotificationsWithError:error]); + + id interceptor2 = OCMProtocolMock(@protocol(UIApplicationDelegate)); + OCMExpect([interceptor2 application:application + didFailToRegisterForRemoteNotificationsWithError:error]); + + GULTestAppDelegate *testAppDelegate = [[GULTestAppDelegate alloc] init]; + [GULAppDelegateSwizzler proxyAppDelegate:testAppDelegate]; + [GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor]; + [GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor2]; + + [testAppDelegate application:application didFailToRegisterForRemoteNotificationsWithError:error]; + OCMVerifyAll(interceptor); + OCMVerifyAll(interceptor2); + + XCTAssertEqual(testAppDelegate.application, application); + XCTAssertEqual(testAppDelegate.failToRegisterForRemoteNotificationsError, error); +} + +- (void)testApplicationDidReceiveRemoteNotificationIsInvokedOnInterceptors { + NSDictionary *notification = @{}; + UIApplication *application = [UIApplication sharedApplication]; + + id interceptor = OCMProtocolMock(@protocol(UIApplicationDelegate)); + OCMExpect([interceptor application:application didReceiveRemoteNotification:notification]); + + id interceptor2 = OCMProtocolMock(@protocol(UIApplicationDelegate)); + OCMExpect([interceptor2 application:application didReceiveRemoteNotification:notification]); + + GULTestAppDelegate *testAppDelegate = [[GULTestAppDelegate alloc] init]; + [GULAppDelegateSwizzler proxyAppDelegate:testAppDelegate]; + [GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor]; + [GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor2]; + + [testAppDelegate application:application didReceiveRemoteNotification:notification]; + OCMVerifyAll(interceptor); + OCMVerifyAll(interceptor2); + + XCTAssertEqual(testAppDelegate.application, application); + XCTAssertEqual(testAppDelegate.remoteNotification, notification); +} + +- (void)testApplicationDidReceiveRemoteNotificationWithCompletionIsInvokedOnInterceptors { + NSDictionary *notification = @{}; + UIApplication *application = [UIApplication sharedApplication]; + void (^completion)(UIBackgroundFetchResult) = ^(UIBackgroundFetchResult result) { + }; + + id interceptor = OCMProtocolMock(@protocol(UIApplicationDelegate)); + OCMExpect([interceptor application:application + didReceiveRemoteNotification:notification + fetchCompletionHandler:completion]); + + id interceptor2 = OCMProtocolMock(@protocol(UIApplicationDelegate)); + OCMExpect([interceptor2 application:application + didReceiveRemoteNotification:notification + fetchCompletionHandler:completion]); + + GULTestAppDelegate *testAppDelegate = [[GULTestAppDelegate alloc] init]; + [GULAppDelegateSwizzler proxyAppDelegate:testAppDelegate]; + [GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor]; + [GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor2]; + + [testAppDelegate application:application + didReceiveRemoteNotification:notification + fetchCompletionHandler:completion]; + OCMVerifyAll(interceptor); + OCMVerifyAll(interceptor2); + + XCTAssertEqual(testAppDelegate.application, application); + XCTAssertEqual(testAppDelegate.remoteNotification, notification); + XCTAssertEqual(testAppDelegate.remoteNotificationCompletionHandler, completion); +} + +- (void)testApplicationDidReceiveRemoteNotificationWithCompletionImplementationIsNotAdded { + // The delegate without application:didReceiveRemoteNotification:fetchCompletionHandler: + // implementation + GULTestInterceptorAppDelegate *legacyDelegate = [[GULTestInterceptorAppDelegate alloc] init]; + + XCTAssertFalse([legacyDelegate + respondsToSelector:@selector(application: + didReceiveRemoteNotification:fetchCompletionHandler:)]); + + [GULAppDelegateSwizzler proxyAppDelegate:legacyDelegate]; + + XCTAssertFalse([legacyDelegate + respondsToSelector:@selector(application: + didReceiveRemoteNotification:fetchCompletionHandler:)]); +} + #pragma mark - Tests to test that Plist flag is honored /** Tests that app delegate proxy is enabled when there is no Info.plist dictionary. */ From dee28d335efd57df3ff3c9d3f68582b94e32ec94 Mon Sep 17 00:00:00 2001 From: Maksym Malyhin Date: Wed, 27 Mar 2019 16:35:50 -0400 Subject: [PATCH 06/24] Revert "FIRAuthAppDelegateProxy: implementation updated to use GULAppDelegateSwizzler [WIP]" This reverts commit b2eabf2954b89ab5cd2c6e2a5c467c8bc224e565. --- .../Auth/Tests/FIRAuthAppDelegateProxyTests.m | 84 +++-- Firebase/Auth/Source/FIRAuth.m | 9 +- .../Auth/Source/FIRAuthAppDelegateProxy.h | 15 +- .../Auth/Source/FIRAuthAppDelegateProxy.m | 334 ++++++++++++++++-- FirebaseAuth.podspec | 1 - 5 files changed, 373 insertions(+), 70 deletions(-) diff --git a/Example/Auth/Tests/FIRAuthAppDelegateProxyTests.m b/Example/Auth/Tests/FIRAuthAppDelegateProxyTests.m index e67ad985467..92871c51e52 100644 --- a/Example/Auth/Tests/FIRAuthAppDelegateProxyTests.m +++ b/Example/Auth/Tests/FIRAuthAppDelegateProxyTests.m @@ -20,16 +20,10 @@ #import #import "FIRAuthAppDelegateProxy.h" -#import - #import NS_ASSUME_NONNULL_BEGIN -@interface GULAppDelegateSwizzler (FIRAuthAppDelegateProxyTests) -+ (void)proxyAppDelegate:(id)appDelegate; -@end - /** @class FIRAuthEmptyAppDelegate @brief A @c UIApplicationDelegate implementation that does nothing. */ @@ -214,6 +208,56 @@ - (void)testSharedInstance { XCTAssertEqual(proxy1, proxy2); } +/** @fn testNilApplication + @brief Tests that initialization fails if the application is nil. + */ +- (void)testNilApplication { + XCTAssertNil([[FIRAuthAppDelegateProxy alloc] initWithApplication:nil]); +} + +/** @fn testNilDelegate + @brief Tests that initialization fails if the application's delegate is nil. + */ +- (void)testNilDelegate { + OCMExpect([_mockApplication delegate]).andReturn(nil); + XCTAssertNil([[FIRAuthAppDelegateProxy alloc] initWithApplication:_mockApplication]); +} + +/** @fn testNonconformingDelegate + @brief Tests that initialization fails if the application's delegate does not conform to + @c UIApplicationDelegate protocol. + */ +- (void)testNonconformingDelegate { + OCMExpect([_mockApplication delegate]).andReturn(@"abc"); + XCTAssertNil([[FIRAuthAppDelegateProxy alloc] initWithApplication:_mockApplication]); +} + +/** @fn testDisabledByBundleEntry + @brief Tests that initialization fails if the proxy is disabled by a bundle entry. + */ +- (void)testDisabledByBundleEntry { + // Swizzle NSBundle's objectForInfoDictionaryKey to return @NO for the specific key. + Method method = class_getInstanceMethod([NSBundle class], @selector(objectForInfoDictionaryKey:)); + __block IMP originalImplementation; + IMP newImplmentation = imp_implementationWithBlock(^id(id object, NSString *key) { + if ([key isEqualToString:@"FirebaseAppDelegateProxyEnabled"]) { + return @NO; + } + typedef id (*Implementation)(id object, SEL cmd, NSString *key); + return ((Implementation)originalImplementation)(object, @selector(objectForInfoDictionaryKey:), + key); + }); + originalImplementation = method_setImplementation(method, newImplmentation); + + // Verify that initialization fails. + FIRAuthEmptyAppDelegate *delegate = [[FIRAuthEmptyAppDelegate alloc] init]; + OCMStub([_mockApplication delegate]).andReturn(delegate); + XCTAssertNil([[FIRAuthAppDelegateProxy alloc] initWithApplication:_mockApplication]); + + // Unswizzle. + imp_removeBlock(method_setImplementation(method, originalImplementation)); +} + // Deprecated methods are call intentionally in tests to verify behaviors on older iOS systems. #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" @@ -224,14 +268,10 @@ - (void)testSharedInstance { - (void)testEmptyDelegateOneHandler { FIRAuthEmptyAppDelegate *delegate = [[FIRAuthEmptyAppDelegate alloc] init]; OCMExpect([_mockApplication delegate]).andReturn(delegate); - - [GULAppDelegateSwizzler proxyAppDelegate:delegate]; - __weak id weakProxy; @autoreleasepool { - FIRAuthAppDelegateProxy *proxy = [[FIRAuthAppDelegateProxy alloc] init]; - [GULAppDelegateSwizzler registerAppDelegateInterceptor:proxy]; - + FIRAuthAppDelegateProxy *proxy = + [[FIRAuthAppDelegateProxy alloc] initWithApplication:_mockApplication]; XCTAssertNotNil(proxy); // Verify certain methods are swizzled while others are not. @@ -351,13 +391,10 @@ - (void)testEmptyDelegateOneHandler { - (void)testLegacyDelegateTwoHandlers { FIRAuthLegacyAppDelegate *delegate = [[FIRAuthLegacyAppDelegate alloc] init]; OCMExpect([_mockApplication delegate]).andReturn(delegate); - - [GULAppDelegateSwizzler proxyAppDelegate:delegate]; - __weak id weakProxy; @autoreleasepool { - FIRAuthAppDelegateProxy *proxy = [[FIRAuthAppDelegateProxy alloc] init]; - [GULAppDelegateSwizzler registerAppDelegateInterceptor:proxy]; + FIRAuthAppDelegateProxy *proxy = + [[FIRAuthAppDelegateProxy alloc] initWithApplication:_mockApplication]; XCTAssertNotNil(proxy); // Verify certain methods are swizzled while others are not. @@ -502,13 +539,10 @@ - (void)testModernDelegateWithUnaffectedInstance { FIRAuthModernAppDelegate *delegate = [[FIRAuthModernAppDelegate alloc] init]; OCMExpect([_mockApplication delegate]).andReturn(delegate); FIRAuthModernAppDelegate *unaffectedDelegate = [[FIRAuthModernAppDelegate alloc] init]; - - [GULAppDelegateSwizzler proxyAppDelegate:delegate]; - __weak id weakProxy; @autoreleasepool { - FIRAuthAppDelegateProxy *proxy = [[FIRAuthAppDelegateProxy alloc] init]; - [GULAppDelegateSwizzler registerAppDelegateInterceptor:proxy]; + FIRAuthAppDelegateProxy *proxy = + [[FIRAuthAppDelegateProxy alloc] initWithApplication:_mockApplication]; XCTAssertNotNil(proxy); // Verify certain methods are swizzled while others are not. @@ -669,12 +703,10 @@ - (void)testModernDelegateWithUnaffectedInstance { - (void)testOtherLegacyDelegateHandleOpenURL { FIRAuthOtherLegacyAppDelegate *delegate = [[FIRAuthOtherLegacyAppDelegate alloc] init]; OCMExpect([_mockApplication delegate]).andReturn(delegate); - [GULAppDelegateSwizzler proxyAppDelegate:delegate]; - __weak id weakProxy; @autoreleasepool { - FIRAuthAppDelegateProxy *proxy = [[FIRAuthAppDelegateProxy alloc] init]; - [GULAppDelegateSwizzler registerAppDelegateInterceptor:proxy]; + FIRAuthAppDelegateProxy *proxy = + [[FIRAuthAppDelegateProxy alloc] initWithApplication:_mockApplication]; XCTAssertNotNil(proxy); // Verify certain methods are swizzled while others are not. diff --git a/Firebase/Auth/Source/FIRAuth.m b/Firebase/Auth/Source/FIRAuth.m index abce76d2500..efb3a487dc6 100644 --- a/Firebase/Auth/Source/FIRAuth.m +++ b/Firebase/Auth/Source/FIRAuth.m @@ -30,7 +30,6 @@ #import #import #import -#import #import "AuthProviders/EmailPassword/FIREmailPasswordAuthCredential.h" #import "FIRAdditionalUserInfo_Internal.h" @@ -374,12 +373,8 @@ - (nullable instancetype)initWithAPIKey:(NSString *)APIKey appName:(NSString *)a } UIApplication *application = [applicationClass sharedApplication]; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - [GULAppDelegateSwizzler proxyOriginalDelegate]; - [GULAppDelegateSwizzler registerAppDelegateInterceptor:[FIRAuthAppDelegateProxy sharedInstance]]; - }); - + // Initialize the shared FIRAuthAppDelegateProxy instance in the main thread if not already. + [FIRAuthAppDelegateProxy sharedInstance]; #endif // Continue with the rest of initialization in the work thread. diff --git a/Firebase/Auth/Source/FIRAuthAppDelegateProxy.h b/Firebase/Auth/Source/FIRAuthAppDelegateProxy.h index fcf1ae35f00..ccae93a7899 100644 --- a/Firebase/Auth/Source/FIRAuthAppDelegateProxy.h +++ b/Firebase/Auth/Source/FIRAuthAppDelegateProxy.h @@ -55,7 +55,20 @@ NS_ASSUME_NONNULL_BEGIN /** @class FIRAuthAppDelegateProxy @brief A manager for swizzling @c UIApplicationDelegate methods. */ -@interface FIRAuthAppDelegateProxy : NSObject +@interface FIRAuthAppDelegateProxy : NSObject + +/** @fn initWithApplication + @brief Initialize the instance with the given @c UIApplication. + @returns An initialized instance, or @c nil if a proxy cannot be established. + @remarks This method should only be called from tests if called outside of this class. + */ +- (nullable instancetype)initWithApplication:(nullable UIApplication *)application + NS_DESIGNATED_INITIALIZER; + +/** @fn init + @brief Call @c sharedInstance to get an instance of this class. + */ +- (instancetype)init NS_UNAVAILABLE; /** @fn addHandler: @brief Adds a handler for UIApplicationDelegate methods. diff --git a/Firebase/Auth/Source/FIRAuthAppDelegateProxy.m b/Firebase/Auth/Source/FIRAuthAppDelegateProxy.m index f949f3da6de..d97fedc6a69 100644 --- a/Firebase/Auth/Source/FIRAuthAppDelegateProxy.m +++ b/Firebase/Auth/Source/FIRAuthAppDelegateProxy.m @@ -16,7 +16,7 @@ #import "FIRAuthAppDelegateProxy.h" -#import +#import #import @@ -28,22 +28,171 @@ */ static NSString *const kProxyEnabledBundleKey = @"FirebaseAppDelegateProxyEnabled"; +/** @fn noop + @brief A function that does nothing. + @remarks This is used as the placeholder for unimplemented UApplicationDelegate methods, + because once we added a method there is no way to remove it from the class. + */ +#if !OBJC_OLD_DISPATCH_PROTOTYPES +static void noop(void) { +} +#else +static id noop(id object, SEL cmd, ...) { + return nil; +} +#endif + +/** @fn isIOS9orLater + @brief Checks whether the iOS version is 9 or later. + @returns Whether the iOS version is 9 or later. + */ +static BOOL isIOS9orLater() { +#if defined(__IPHONE_11_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_11_0) + if (@available(iOS 9.0, *)) { + return YES; + } + return NO; +#else + // UIApplicationOpenURLOptionsAnnotationKey is only available on iOS 9+. + return &UIApplicationOpenURLOptionsAnnotationKey != NULL; +#endif +} + @implementation FIRAuthAppDelegateProxy { + /** @var _appDelegate + @brief The application delegate whose method is being swizzled. + */ + id _appDelegate; + + /** @var _orginalImplementationsBySelector + @brief A map from selectors to original implementations that have been swizzled. + */ + NSMutableDictionary *_originalImplementationsBySelector; + /** @var _handlers @brief The array of weak pointers of `id`. */ NSPointerArray *_handlers; } -- (instancetype)init -{ +- (nullable instancetype)initWithApplication:(nullable UIApplication *)application { self = [super init]; if (self) { + id proxyEnabled = [[NSBundle mainBundle] objectForInfoDictionaryKey:kProxyEnabledBundleKey]; + if ([proxyEnabled isKindOfClass:[NSNumber class]] && !((NSNumber *)proxyEnabled).boolValue) { + return nil; + } + _appDelegate = application.delegate; + if (![_appDelegate conformsToProtocol:@protocol(UIApplicationDelegate)]) { + return nil; + } + _originalImplementationsBySelector = [[NSMutableDictionary alloc] init]; _handlers = [[NSPointerArray alloc] initWithOptions:NSPointerFunctionsWeakMemory]; + + // Swizzle the methods. + __weak FIRAuthAppDelegateProxy *weakSelf = self; + SEL registerDeviceTokenSelector = + @selector(application:didRegisterForRemoteNotificationsWithDeviceToken:); + [self replaceSelector:registerDeviceTokenSelector + withBlock:^(id object, UIApplication* application, NSData *deviceToken) { + [weakSelf object:object + selector:registerDeviceTokenSelector + application:application + didRegisterForRemoteNotificationsWithDeviceToken:deviceToken]; + }]; + SEL failToRegisterRemoteNotificationSelector = + @selector(application:didFailToRegisterForRemoteNotificationsWithError:); + [self replaceSelector:failToRegisterRemoteNotificationSelector + withBlock:^(id object, UIApplication* application, NSError *error) { + [weakSelf object:object + selector:failToRegisterRemoteNotificationSelector + application:application + didFailToRegisterForRemoteNotificationsWithError:error]; + }]; + SEL receiveNotificationSelector = @selector(application:didReceiveRemoteNotification:); + SEL receiveNotificationWithHandlerSelector = + @selector(application:didReceiveRemoteNotification:fetchCompletionHandler:); + if ([_appDelegate respondsToSelector:receiveNotificationWithHandlerSelector] || + ![_appDelegate respondsToSelector:receiveNotificationSelector]) { + // Replace the modern selector which is available on iOS 7 and above. + [self replaceSelector:receiveNotificationWithHandlerSelector + withBlock:^(id object, UIApplication *application, NSDictionary *notification, + void (^completionHandler)(UIBackgroundFetchResult)) { + [weakSelf object:object + selector:receiveNotificationWithHandlerSelector + application:application + didReceiveRemoteNotification:notification + fetchCompletionHandler:completionHandler]; + }]; + } else { + // Replace the deprecated selector because this is the only one that the client app uses. + [self replaceSelector:receiveNotificationSelector + withBlock:^(id object, UIApplication *application, NSDictionary *notification) { + [weakSelf object:object + selector:receiveNotificationSelector + application:application + didReceiveRemoteNotification:notification]; + }]; + } + SEL openURLOptionsSelector = @selector(application:openURL:options:); + SEL openURLAnnotationSelector = @selector(application:openURL:sourceApplication:annotation:); + SEL handleOpenURLSelector = @selector(application:handleOpenURL:); + if (isIOS9orLater() && + ([_appDelegate respondsToSelector:openURLOptionsSelector] || + (![_appDelegate respondsToSelector:openURLAnnotationSelector] && + ![_appDelegate respondsToSelector:handleOpenURLSelector]))) { + // Replace the modern selector which is avaliable on iOS 9 and above because this is the one + // that the client app uses or the client app doesn't use any of them. + [self replaceSelector:openURLOptionsSelector + withBlock:^BOOL(id object, UIApplication *application, NSURL *url, + NSDictionary *options) { + return [weakSelf object:object + selector:openURLOptionsSelector + application:application + openURL:url + options:options]; + }]; + } else if ([_appDelegate respondsToSelector:openURLAnnotationSelector] || + ![_appDelegate respondsToSelector:handleOpenURLSelector]) { + // Replace the longer form of the deprecated selectors on iOS 8 and below because this is the + // one that the client app uses or the client app doesn't use either of the applicable ones. + [self replaceSelector:openURLAnnotationSelector + withBlock:^(id object, UIApplication *application, NSURL *url, + NSString *sourceApplication, id annotation) { + return [weakSelf object:object + selector:openURLAnnotationSelector + application:application + openURL:url + sourceApplication:sourceApplication + annotation:annotation]; + }]; + } else { + // Replace the shorter form of the deprecated selectors on iOS 8 and below because this is + // the only one that the client app uses. + [self replaceSelector:handleOpenURLSelector + withBlock:^(id object, UIApplication *application, NSURL *url) { + return [weakSelf object:object + selector:handleOpenURLSelector + application:application + handleOpenURL:url]; + }]; + } + // Reset the application delegate to clear the system cache that indicates whether each of the + // openURL: methods is implemented on the application delegate. + application.delegate = nil; + application.delegate = _appDelegate; } return self; } +- (void)dealloc { + for (NSValue *selector in _originalImplementationsBySelector) { + IMP implementation = _originalImplementationsBySelector[selector].pointerValue; + Method method = class_getInstanceMethod([_appDelegate class], selector.pointerValue); + imp_removeBlock(method_setImplementation(method, implementation)); + } +} + - (void)addHandler:(__weak id)handler { @synchronized (_handlers) { [_handlers addPointer:(__bridge void *)handler]; @@ -53,59 +202,143 @@ - (void)addHandler:(__weak id)handler { + (nullable instancetype)sharedInstance { static dispatch_once_t onceToken; static FIRAuthAppDelegateProxy *_Nullable sharedInstance; + // iOS App extensions should not call [UIApplication sharedApplication], even if UIApplication + // responds to it. + static Class applicationClass = nil; dispatch_once(&onceToken, ^{ - sharedInstance = [[FIRAuthAppDelegateProxy alloc] init]; + if (![GULAppEnvironmentUtil isAppExtension]) { + Class cls = NSClassFromString(@"UIApplication"); + if (cls && [cls respondsToSelector:NSSelectorFromString(@"sharedApplication")]) { + applicationClass = cls; + } + } + UIApplication *application = [applicationClass sharedApplication]; + sharedInstance = [[self alloc] initWithApplication:application]; }); return sharedInstance; } #pragma mark - UIApplicationDelegate proxy methods. -- (void)application:(UIApplication *)application -didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { - for (id handler in [self handlers]) { - [handler setAPNSToken:deviceToken]; +- (void)object:(id)object + selector:(SEL)selector + application:(UIApplication *)application + didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { + if (object == _appDelegate) { + for (id handler in [self handlers]) { + [handler setAPNSToken:deviceToken]; + } + } + IMP originalImplementation = [self originalImplementationForSelector:selector]; + if (originalImplementation && originalImplementation != &noop) { + typedef void (*Implmentation)(id, SEL, UIApplication*, NSData *); + ((Implmentation)originalImplementation)(object, selector, application, deviceToken); } } -- (void)application:(UIApplication *)application -didFailToRegisterForRemoteNotificationsWithError:(NSError *)error { - for (id handler in [self handlers]) { - [handler handleAPNSTokenError:error]; +- (void)object:(id)object + selector:(SEL)selector + application:(UIApplication *)application + didFailToRegisterForRemoteNotificationsWithError:(NSError *)error { + if (object == _appDelegate) { + for (id handler in [self handlers]) { + [handler handleAPNSTokenError:error]; + } + } + IMP originalImplementation = [self originalImplementationForSelector:selector]; + if (originalImplementation && originalImplementation != &noop) { + typedef void (*Implmentation)(id, SEL, UIApplication *, NSError *); + ((Implmentation)originalImplementation)(object, selector, application, error); } } -- (void)application:(UIApplication *)application -didReceiveRemoteNotification:(NSDictionary *)userInfo -fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { - for (id handler in [self handlers]) { - if ([handler canHandleNotification:userInfo]) { - completionHandler(UIBackgroundFetchResultNoData); - return; - }; +- (void)object:(id)object + selector:(SEL)selector + application:(UIApplication *)application + didReceiveRemoteNotification:(NSDictionary *)notification + fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { + if (object == _appDelegate) { + for (id handler in [self handlers]) { + if ([handler canHandleNotification:notification]) { + completionHandler(UIBackgroundFetchResultNoData); + return; + }; + } + } + IMP originalImplementation = [self originalImplementationForSelector:selector]; + if (originalImplementation && originalImplementation != &noop) { + typedef void (*Implmentation)(id, SEL, UIApplication*, NSDictionary *, + void (^)(UIBackgroundFetchResult)); + ((Implmentation)originalImplementation)(object, selector, application, notification, + completionHandler); } } -- (void)application:(UIApplication *)application -didReceiveRemoteNotification:(NSDictionary *)userInfo { - for (id handler in [self handlers]) { - if ([handler canHandleNotification:userInfo]) { - return; - }; +- (void)object:(id)object + selector:(SEL)selector + application:(UIApplication *)application + didReceiveRemoteNotification:(NSDictionary *)notification { + if (object == _appDelegate) { + for (id handler in [self handlers]) { + if ([handler canHandleNotification:notification]) { + return; + }; + } + } + IMP originalImplementation = [self originalImplementationForSelector:selector]; + if (originalImplementation && originalImplementation != &noop) { + typedef void (*Implmentation)(id, SEL, UIApplication*, NSDictionary *); + ((Implmentation)originalImplementation)(object, selector, application, notification); } } -- (BOOL)application:(UIApplication *)app - openURL:(NSURL *)url - options:(NSDictionary *)options { - return [self delegateCanHandleURL:url]; +- (BOOL)object:(id)object + selector:(SEL)selector + application:(UIApplication *)application + openURL:(NSURL *)url + options:(NSDictionary *)options { + if (object == _appDelegate && [self delegateCanHandleURL:url]) { + return YES; + } + IMP originalImplementation = [self originalImplementationForSelector:selector]; + if (originalImplementation && originalImplementation != &noop) { + typedef BOOL (*Implmentation)(id, SEL, UIApplication*, NSURL *, NSDictionary *); + return ((Implmentation)originalImplementation)(object, selector, application, url, options); + } + return NO; } -- (BOOL)application:(UIApplication *)application - openURL:(NSURL *)url - sourceApplication:(nullable NSString *)sourceApplication - annotation:(id)annotation { - return [self delegateCanHandleURL:url]; +- (BOOL)object:(id)object + selector:(SEL)selector + application:(UIApplication *)application + openURL:(NSURL *)url + sourceApplication:(NSString *)sourceApplication + annotation:(id)annotation { + if (object == _appDelegate && [self delegateCanHandleURL:url]) { + return YES; + } + IMP originalImplementation = [self originalImplementationForSelector:selector]; + if (originalImplementation && originalImplementation != &noop) { + typedef BOOL (*Implmentation)(id, SEL, UIApplication*, NSURL *, NSString *, id); + return ((Implmentation)originalImplementation)(object, selector, application, url, + sourceApplication, annotation); + } + return NO; +} + +- (BOOL)object:(id)object + selector:(SEL)selector + application:(UIApplication *)application + handleOpenURL:(NSURL *)url { + if (object == _appDelegate && [self delegateCanHandleURL:url]) { + return YES; + } + IMP originalImplementation = [self originalImplementationForSelector:selector]; + if (originalImplementation && originalImplementation != &noop) { + typedef BOOL (*Implmentation)(id, SEL, UIApplication*, NSURL *); + return ((Implmentation)originalImplementation)(object, selector, application, url); + } + return NO; } #pragma mark - Internal Methods @@ -143,6 +376,37 @@ - (BOOL)delegateCanHandleURL:(NSURL *)url { } } +/** @fn replaceSelector:withBlock: + @brief replaces the implementation for a method of `_appDelegate` specified by a selector. + @param selector The selector for the method. + @param block The block as the new implementation of the method. + */ +- (void)replaceSelector:(SEL)selector withBlock:(id)block { + Method originalMethod = class_getInstanceMethod([_appDelegate class], selector); + IMP newImplementation = imp_implementationWithBlock(block); + IMP originalImplementation; + if (originalMethod) { + originalImplementation = method_setImplementation(originalMethod, newImplementation) ?: &noop; + } else { + // The original method was not implemented in the class, add it with the new implementation. + struct objc_method_description methodDescription = + protocol_getMethodDescription(@protocol(UIApplicationDelegate), selector, NO, YES); + class_addMethod([_appDelegate class], selector, newImplementation, methodDescription.types); + originalImplementation = &noop; + } + _originalImplementationsBySelector[[NSValue valueWithPointer:selector]] = + [NSValue valueWithPointer:originalImplementation]; +} + +/** @fn originalImplementationForSelector: + @brief Gets the original implementation for the given selector. + @param selector The selector for the method that has been replaced. + @return The original implementation if there was one. + */ +- (IMP)originalImplementationForSelector:(SEL)selector { + return _originalImplementationsBySelector[[NSValue valueWithPointer:selector]].pointerValue; +} + @end NS_ASSUME_NONNULL_END diff --git a/FirebaseAuth.podspec b/FirebaseAuth.podspec index bd5a4f44b3b..9dd85a9c168 100644 --- a/FirebaseAuth.podspec +++ b/FirebaseAuth.podspec @@ -64,7 +64,6 @@ supports email and password accounts, as well as several 3rd party authenticatio s.ios.framework = 'SafariServices' s.dependency 'FirebaseAuthInterop', '~> 1.0' s.dependency 'FirebaseCore', '~> 5.2' - s.dependency 'GoogleUtilities/AppDelegateSwizzler', '~> 5.2' s.dependency 'GoogleUtilities/Environment', '~> 5.2' s.dependency 'GTMSessionFetcher/Core', '~> 1.1' end From 5821591cb4404f161f9b6de0c11447a67ea9dad9 Mon Sep 17 00:00:00 2001 From: Maksym Malyhin Date: Wed, 27 Mar 2019 17:29:13 -0400 Subject: [PATCH 07/24] FIRMessaging: prepare to use GULAppDelegateSwizzler [WIP] --- Firebase/Messaging/FIRMessaging.m | 2 + .../FIRMessagingApplicationDelegateProxy.h | 27 +++++++++ .../FIRMessagingApplicationDelegateProxy.m | 60 +++++++++++++++++++ 3 files changed, 89 insertions(+) create mode 100644 Firebase/Messaging/FIRMessagingApplicationDelegateProxy.h create mode 100644 Firebase/Messaging/FIRMessagingApplicationDelegateProxy.m diff --git a/Firebase/Messaging/FIRMessaging.m b/Firebase/Messaging/FIRMessaging.m index 6a9870281f6..b15fac28683 100644 --- a/Firebase/Messaging/FIRMessaging.m +++ b/Firebase/Messaging/FIRMessaging.m @@ -260,6 +260,8 @@ - (void)configureMessaging:(FIRApp *)app { kFIRMessagingRemoteNotificationsProxyEnabledInfoPlistKey, docsURLString); [FIRMessagingRemoteNotificationsProxy swizzleMethods]; + + . } } diff --git a/Firebase/Messaging/FIRMessagingApplicationDelegateProxy.h b/Firebase/Messaging/FIRMessagingApplicationDelegateProxy.h new file mode 100644 index 00000000000..5aa6576ff3e --- /dev/null +++ b/Firebase/Messaging/FIRMessagingApplicationDelegateProxy.h @@ -0,0 +1,27 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface FIRMessagingApplicationDelegateProxy : NSObject + ++ (instancetype)shared; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Firebase/Messaging/FIRMessagingApplicationDelegateProxy.m b/Firebase/Messaging/FIRMessagingApplicationDelegateProxy.m new file mode 100644 index 00000000000..3c6aa2321e0 --- /dev/null +++ b/Firebase/Messaging/FIRMessagingApplicationDelegateProxy.m @@ -0,0 +1,60 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRMessagingApplicationDelegateProxy.h" + +#import "FIRMessagingLogger.h" +#import "FIRMessaging_Private.h" + +@implementation FIRMessagingApplicationDelegateProxy + ++ (instancetype)shared { + static FIRMessagingApplicationDelegateProxy *sharedInstance = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + sharedInstance = [[FIRMessagingApplicationDelegateProxy alloc] init]; + }); + + return sharedInstance; +} + +- (void)application:(UIApplication *)application +didReceiveRemoteNotification:(NSDictionary *)userInfo { + [[FIRMessaging messaging] appDidReceiveMessage:userInfo]; +} + +- (void)application:(UIApplication *)application +didReceiveRemoteNotification:(NSDictionary *)userInfo +fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { + [[FIRMessaging messaging] appDidReceiveMessage:userInfo]; +} + +- (void)application:(UIApplication *)application +didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { + // Pass the APNSToken along to FIRMessaging (and auto-detect the token type) + [FIRMessaging messaging].APNSToken = deviceToken; +} + +- (void)application:(UIApplication *)application +didFailToRegisterForRemoteNotificationsWithError:(NSError *)error { + // Log the fact that we failed to register for remote notifications + FIRMessagingLoggerError(kFIRMessagingMessageCodeRemoteNotificationsProxyAPNSFailed, + @"Error in " + @"application:didFailToRegisterForRemoteNotificationsWithError: %@", + error.localizedDescription); +} + +@end From 908b6ea9f78ab4c56ca0e76e6946e73283a27c6c Mon Sep 17 00:00:00 2001 From: Maksym Malyhin Date: Thu, 28 Mar 2019 11:11:18 -0400 Subject: [PATCH 08/24] FIRMessagingRemoteNotificationsProxy - use GULAppDelegateSwizzler [WIP] --- Firebase/Messaging/FIRMessaging.m | 2 - .../FIRMessagingRemoteNotificationsProxy.m | 98 +++++++------------ .../Internal/GULAppDelegateSwizzler_Private.h | 6 -- .../Private/GULAppDelegateSwizzler.h | 7 ++ 4 files changed, 45 insertions(+), 68 deletions(-) diff --git a/Firebase/Messaging/FIRMessaging.m b/Firebase/Messaging/FIRMessaging.m index b15fac28683..6a9870281f6 100644 --- a/Firebase/Messaging/FIRMessaging.m +++ b/Firebase/Messaging/FIRMessaging.m @@ -260,8 +260,6 @@ - (void)configureMessaging:(FIRApp *)app { kFIRMessagingRemoteNotificationsProxyEnabledInfoPlistKey, docsURLString); [FIRMessagingRemoteNotificationsProxy swizzleMethods]; - - . } } diff --git a/Firebase/Messaging/FIRMessagingRemoteNotificationsProxy.m b/Firebase/Messaging/FIRMessagingRemoteNotificationsProxy.m index 7cea178edc8..a90dad4e70e 100644 --- a/Firebase/Messaging/FIRMessagingRemoteNotificationsProxy.m +++ b/Firebase/Messaging/FIRMessagingRemoteNotificationsProxy.m @@ -23,8 +23,8 @@ #import "FIRMessagingLogger.h" #import "FIRMessagingUtilities.h" #import "FIRMessaging_Private.h" +#import -static const BOOL kDefaultAutoRegisterEnabledValue = YES; static void * UserNotificationObserverContext = &UserNotificationObserverContext; static NSString *kUserNotificationWillPresentSelectorString = @@ -33,7 +33,7 @@ @"userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:"; static NSString *kReceiveDataMessageSelectorString = @"messaging:didReceiveMessage:"; -@interface FIRMessagingRemoteNotificationsProxy () +@interface FIRMessagingRemoteNotificationsProxy () @property(strong, nonatomic) NSMutableDictionary *originalAppDelegateImps; @property(strong, nonatomic) NSMutableDictionary *swizzledSelectorsByClass; @@ -47,20 +47,14 @@ @interface FIRMessagingRemoteNotificationsProxy () @property(strong, nonatomic) id userNotificationCenter; @property(strong, nonatomic) id currentUserNotificationCenterDelegate; +@property(strong, nonatomic) GULAppDelegateInterceptorID appDelegateInterceptorID; + @end @implementation FIRMessagingRemoteNotificationsProxy + (BOOL)canSwizzleMethods { - id canSwizzleValue = - [[NSBundle mainBundle] - objectForInfoDictionaryKey: kFIRMessagingRemoteNotificationsProxyEnabledInfoPlistKey]; - if (canSwizzleValue && [canSwizzleValue isKindOfClass:[NSNumber class]]) { - NSNumber *canSwizzleNumberValue = (NSNumber *)canSwizzleValue; - return canSwizzleNumberValue.boolValue; - } else { - return kDefaultAutoRegisterEnabledValue; - } + return [GULAppDelegateSwizzler isAppDelegateProxyEnabled]; } + (void)swizzleMethods { @@ -99,7 +93,10 @@ - (void)swizzleMethodsIfPossible { return; } - UIApplication *application = FIRMessagingUIApplication(); + [GULAppDelegateSwizzler proxyOriginalDelegate]; + self.appDelegateInterceptorID = [GULAppDelegateSwizzler registerAppDelegateInterceptor:self]; + + UIApplication *application = [GULAppDelegateSwizzler sharedApplication]; if (!application) { return; } @@ -122,6 +119,7 @@ - (void)swizzleMethodsIfPossible { } - (void)unswizzleAllMethods { + [GULAppDelegateSwizzler unregisterAppDelegateInterceptorWithID:self.appDelegateInterceptorID]; for (NSString *className in self.swizzledSelectorsByClass) { Class klass = NSClassFromString(className); NSArray *selectorStrings = self.swizzledSelectorsByClass[className]; @@ -134,46 +132,9 @@ - (void)unswizzleAllMethods { } - (void)swizzleAppDelegateMethods:(id)appDelegate { - if (![appDelegate conformsToProtocol:@protocol(UIApplicationDelegate)]) { - return; - } Class appDelegateClass = [appDelegate class]; BOOL didSwizzleAppDelegate = NO; - // Message receiving handler for iOS 9, 8, 7 devices (both display notification and data message). - SEL remoteNotificationSelector = - @selector(application:didReceiveRemoteNotification:); - - SEL remoteNotificationWithFetchHandlerSelector = - @selector(application:didReceiveRemoteNotification:fetchCompletionHandler:); - - // For recording when APNS tokens are registered (or fail to register) - SEL registerForAPNSFailSelector = - @selector(application:didFailToRegisterForRemoteNotificationsWithError:); - - SEL registerForAPNSSuccessSelector = - @selector(application:didRegisterForRemoteNotificationsWithDeviceToken:); - - - // Receive Remote Notifications. - BOOL selectorWithFetchHandlerImplemented = NO; - if ([appDelegate respondsToSelector:remoteNotificationWithFetchHandlerSelector]) { - selectorWithFetchHandlerImplemented = YES; - [self swizzleSelector:remoteNotificationWithFetchHandlerSelector - inClass:appDelegateClass - withImplementation:(IMP)FCM_swizzle_appDidReceiveRemoteNotificationWithHandler - inProtocol:@protocol(UIApplicationDelegate)]; - didSwizzleAppDelegate = YES; - } - - if ([appDelegate respondsToSelector:remoteNotificationSelector] || - !selectorWithFetchHandlerImplemented) { - [self swizzleSelector:remoteNotificationSelector - inClass:appDelegateClass - withImplementation:(IMP)FCM_swizzle_appDidReceiveRemoteNotification - inProtocol:@protocol(UIApplicationDelegate)]; - didSwizzleAppDelegate = YES; - } // For data message from MCS. SEL receiveDataMessageSelector = NSSelectorFromString(kReceiveDataMessageSelectorString); @@ -185,17 +146,6 @@ - (void)swizzleAppDelegateMethods:(id)appDelegate { didSwizzleAppDelegate = YES; } - // Receive APNS token - [self swizzleSelector:registerForAPNSSuccessSelector - inClass:appDelegateClass - withImplementation:(IMP)FCM_swizzle_appDidRegisterForRemoteNotifications - inProtocol:@protocol(UIApplicationDelegate)]; - - [self swizzleSelector:registerForAPNSFailSelector - inClass:appDelegateClass - withImplementation:(IMP)FCM_swizzle_appDidFailToRegisterForRemoteNotifications - inProtocol:@protocol(UIApplicationDelegate)]; - self.didSwizzleAppDelegateMethods = didSwizzleAppDelegate; } @@ -470,6 +420,34 @@ id getNamedPropertyFromObject(id object, NSString *propertyName, Class klass) { return property; } +#pragma mark - UIApplicationDelegate + +- (void)application:(UIApplication *)application +didReceiveRemoteNotification:(NSDictionary *)userInfo { + [[FIRMessaging messaging] appDidReceiveMessage:userInfo]; +} + +- (void)application:(UIApplication *)application +didReceiveRemoteNotification:(NSDictionary *)userInfo +fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { + [[FIRMessaging messaging] appDidReceiveMessage:userInfo]; +} + +- (void)application:(UIApplication *)application +didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { + // Pass the APNSToken along to FIRMessaging (and auto-detect the token type) + [FIRMessaging messaging].APNSToken = deviceToken; +} + +- (void)application:(UIApplication *)application +didFailToRegisterForRemoteNotificationsWithError:(NSError *)error { + // Log the fact that we failed to register for remote notifications + FIRMessagingLoggerError(kFIRMessagingMessageCodeRemoteNotificationsProxyAPNSFailed, + @"Error in " + @"application:didFailToRegisterForRemoteNotificationsWithError: %@", + error.localizedDescription); +} + #pragma mark - Swizzled Methods void FCM_swizzle_appDidReceiveRemoteNotification(id self, diff --git a/GoogleUtilities/AppDelegateSwizzler/Internal/GULAppDelegateSwizzler_Private.h b/GoogleUtilities/AppDelegateSwizzler/Internal/GULAppDelegateSwizzler_Private.h index 219b220cfd9..6e6b556e7ef 100644 --- a/GoogleUtilities/AppDelegateSwizzler/Internal/GULAppDelegateSwizzler_Private.h +++ b/GoogleUtilities/AppDelegateSwizzler/Internal/GULAppDelegateSwizzler_Private.h @@ -23,12 +23,6 @@ NS_ASSUME_NONNULL_BEGIN @interface GULAppDelegateSwizzler () -/** Returns the current sharedApplication. - * - * @return the current UIApplication if in an app, or nil if in extension or if it doesn't exist. - */ -+ (nullable UIApplication *)sharedApplication; - /** ISA Swizzles the given appDelegate as the original app delegate would be. * * @param appDelegate The object that needs to be isa swizzled. This should conform to the diff --git a/GoogleUtilities/AppDelegateSwizzler/Private/GULAppDelegateSwizzler.h b/GoogleUtilities/AppDelegateSwizzler/Private/GULAppDelegateSwizzler.h index 31fc4b0ab04..4421766458b 100644 --- a/GoogleUtilities/AppDelegateSwizzler/Private/GULAppDelegateSwizzler.h +++ b/GoogleUtilities/AppDelegateSwizzler/Private/GULAppDelegateSwizzler.h @@ -16,6 +16,7 @@ #import +@class UIApplication; @protocol UIApplicationDelegate; NS_ASSUME_NONNULL_BEGIN @@ -55,6 +56,12 @@ typedef NSString *const GULAppDelegateInterceptorID; */ + (BOOL)isAppDelegateProxyEnabled; +/** Returns the current sharedApplication. + * + * @return the current UIApplication if in an app, or nil if in extension or if it doesn't exist. + */ ++ (nullable UIApplication *)sharedApplication; + /** Do not initialize this class. */ - (instancetype)init NS_UNAVAILABLE; From 9e377d251e8fdaba6041fa055fe876764c302a5b Mon Sep 17 00:00:00 2001 From: Maksym Malyhin Date: Thu, 28 Mar 2019 14:53:30 -0400 Subject: [PATCH 09/24] FIRMessagingRemoteNotificationsProxy - AppDelegate proxy - test only public interface with no assumptions on the implementation details --- ...FIRMessagingRemoteNotificationsProxyTest.m | 106 +++++++++++------- Firebase/Messaging/FIRMessaging.m | 2 +- .../FIRMessagingRemoteNotificationsProxy.h | 7 +- 3 files changed, 74 insertions(+), 41 deletions(-) diff --git a/Example/Messaging/Tests/FIRMessagingRemoteNotificationsProxyTest.m b/Example/Messaging/Tests/FIRMessagingRemoteNotificationsProxyTest.m index 7a91dd68bd3..84618f230ba 100644 --- a/Example/Messaging/Tests/FIRMessagingRemoteNotificationsProxyTest.m +++ b/Example/Messaging/Tests/FIRMessagingRemoteNotificationsProxyTest.m @@ -28,26 +28,16 @@ // Expose some internal properties and methods here, in order to test @interface FIRMessagingRemoteNotificationsProxy () -@property(readonly, nonatomic) BOOL didSwizzleMethods; -@property(readonly, nonatomic) BOOL didSwizzleAppDelegateMethods; - @property(readonly, nonatomic) BOOL hasSwizzledUserNotificationDelegate; @property(readonly, nonatomic) BOOL isObservingUserNotificationDelegateChanges; @property(strong, readonly, nonatomic) id userNotificationCenter; @property(strong, readonly, nonatomic) id currentUserNotificationCenterDelegate; -+ (instancetype)sharedProxy; - -- (BOOL)swizzleAppDelegateMethods:(id)appDelegate; - (void)listenForDelegateChangesInUserNotificationCenter:(id)notificationCenter; - (void)swizzleUserNotificationCenterDelegate:(id)delegate; - (void)unswizzleUserNotificationCenterDelegate:(id)delegate; -void FCM_swizzle_appDidReceiveRemoteNotification(id self, - SEL _cmd, - UIApplication *app, - NSDictionary *userInfo); void FCM_swizzle_appDidReceiveRemoteNotificationWithHandler( id self, SEL _cmd, UIApplication *app, NSDictionary *userInfo, void (^handler)(UIBackgroundFetchResult)); @@ -58,6 +48,17 @@ void FCM_swizzle_didReceiveNotificationResponseWithHandler( @end +#pragma mark - Invalid App Delegate + +@interface InvalidAppDelegate : NSObject +@end +@implementation InvalidAppDelegate +- (void)application:(UIApplication *)application +didReceiveRemoteNotification:(NSDictionary *)userInfo +fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { +} +@end + #pragma mark - Incomplete App Delegate @interface IncompleteAppDelegate : NSObject @end @@ -119,6 +120,8 @@ @interface FIRMessagingRemoteNotificationsProxyTest : XCTestCase @property(nonatomic, strong) id mockProxy; @property(nonatomic, strong) id mockProxyClass; @property(nonatomic, strong) id mockMessagingClass; +@property(nonatomic, strong) id mockSharedApplication; +@property(nonatomic, strong) id mockMessaging; @end @@ -126,15 +129,16 @@ @implementation FIRMessagingRemoteNotificationsProxyTest - (void)setUp { [super setUp]; + _mockSharedApplication = OCMPartialMock([UIApplication sharedApplication]); + + _mockMessaging = OCMClassMock([FIRMessaging class]); + OCMStub([_mockMessaging messaging]).andReturn(_mockMessaging); + _proxy = [[FIRMessagingRemoteNotificationsProxy alloc] init]; _mockProxy = OCMPartialMock(_proxy); _mockProxyClass = OCMClassMock([FIRMessagingRemoteNotificationsProxy class]); - // Update +sharedProxy to always return our partial mock of FIRMessagingRemoteNotificationsProxy - OCMStub([_mockProxyClass sharedProxy]).andReturn(_mockProxy); - // Many of our swizzled methods call [FIRMessaging messaging], but we don't need it, - // so just stub it to return nil - _mockMessagingClass = OCMClassMock([FIRMessaging class]); - OCMStub([_mockMessagingClass messaging]).andReturn(nil); + // Update +sharedProxy to always return our test instance + OCMStub([_mockProxyClass sharedProxy]).andReturn(self.proxy); } - (void)tearDown { @@ -147,6 +151,12 @@ - (void)tearDown { [_mockProxy stopMocking]; _mockProxy = nil; + [_mockMessaging stopMocking]; + _mockMessaging = nil; + + [_mockSharedApplication stopMocking]; + _mockSharedApplication = nil; + _proxy = nil; [super tearDown]; } @@ -154,30 +164,34 @@ - (void)tearDown { #pragma mark - Method Swizzling Tests - (void)testSwizzlingNonAppDelegate { - id randomObject = @"Random Object that is not an App Delegate"; - [self.proxy swizzleAppDelegateMethods:randomObject]; - XCTAssertFalse(self.proxy.didSwizzleAppDelegateMethods); -} + InvalidAppDelegate *invalidAppDelegate = [[InvalidAppDelegate alloc] init]; + [OCMStub([self.mockSharedApplication delegate]) andReturn:invalidAppDelegate]; + [self.proxy swizzleMethodsIfPossible]; -- (void)testSwizzlingAppDelegate { - IncompleteAppDelegate *incompleteAppDelegate = [[IncompleteAppDelegate alloc] init]; - [self.proxy swizzleAppDelegateMethods:incompleteAppDelegate]; - XCTAssertTrue(self.proxy.didSwizzleAppDelegateMethods); + OCMReject([self.mockMessaging appDidReceiveMessage:[OCMArg any]]); + + [invalidAppDelegate application:self.mockSharedApplication + didReceiveRemoteNotification:@{} + fetchCompletionHandler:^(UIBackgroundFetchResult result) {}]; } - (void)testSwizzledIncompleteAppDelegateRemoteNotificationMethod { - IncompleteAppDelegate *incompleteAppDelegate = [[IncompleteAppDelegate alloc] init]; - [self.mockProxy swizzleAppDelegateMethods:incompleteAppDelegate]; + IncompleteAppDelegate *incompleteAppDelegate = [[IncompleteAppDelegate alloc] init]; + [OCMStub([self.mockSharedApplication delegate]) andReturn:incompleteAppDelegate]; + [self.proxy swizzleMethodsIfPossible]; + + NSDictionary *notification = @{@"test" : @""}; + OCMExpect([self.mockMessaging appDidReceiveMessage:notification]); - [incompleteAppDelegate application:OCMClassMock([UIApplication class]) - didReceiveRemoteNotification:@{}]; - // Verify our swizzled method was called - OCMVerify(FCM_swizzle_appDidReceiveRemoteNotification); + [incompleteAppDelegate application:self.mockSharedApplication + didReceiveRemoteNotification:notification]; } - (void)testIncompleteAppDelegateRemoteNotificationWithFetchHandlerMethod { IncompleteAppDelegate *incompleteAppDelegate = [[IncompleteAppDelegate alloc] init]; - [self.mockProxy swizzleAppDelegateMethods:incompleteAppDelegate]; + [OCMStub([self.mockSharedApplication delegate]) andReturn:incompleteAppDelegate]; + [self.proxy swizzleMethodsIfPossible]; + SEL remoteNotificationWithFetchHandler = @selector(application:didReceiveRemoteNotification:fetchCompletionHandler:); XCTAssertFalse([incompleteAppDelegate respondsToSelector:remoteNotificationWithFetchHandler]); @@ -188,23 +202,37 @@ - (void)testIncompleteAppDelegateRemoteNotificationWithFetchHandlerMethod { - (void)testSwizzledAppDelegateRemoteNotificationMethods { #if TARGET_OS_IOS FakeAppDelegate *appDelegate = [[FakeAppDelegate alloc] init]; - [self.mockProxy swizzleAppDelegateMethods:appDelegate]; - [appDelegate application:OCMClassMock([UIApplication class]) didReceiveRemoteNotification:@{}]; + [OCMStub([self.mockSharedApplication delegate]) andReturn:appDelegate]; + [self.proxy swizzleMethodsIfPossible]; + + NSDictionary *notification = @{@"test" : @""}; + + // application:didReceiveRemoteNotification: + // Verify our swizzled method was called - OCMVerify(FCM_swizzle_appDidReceiveRemoteNotification); + OCMExpect([self.mockMessaging appDidReceiveMessage:notification]); + + // Call the method + [appDelegate application:self.mockSharedApplication + didReceiveRemoteNotification:notification]; + // Verify our original method was called XCTAssertTrue(appDelegate.remoteNotificationMethodWasCalled); + [self.mockMessaging verify]; + + // application:didReceiveRemoteNotification:fetchCompletionHandler: + + // Verify our swizzled method was called + OCMExpect([self.mockMessaging appDidReceiveMessage:notification]); - // Now call the remote notification with handler method [appDelegate application:OCMClassMock([UIApplication class]) - didReceiveRemoteNotification:@{} + didReceiveRemoteNotification:notification fetchCompletionHandler:^(UIBackgroundFetchResult result) {}]; - // Verify our swizzled method was called - OCMVerify(FCM_swizzle_appDidReceiveRemoteNotificationWithHandler); + // Verify our original method was called XCTAssertTrue(appDelegate.remoteNotificationWithFetchHandlerWasCalled); + [self.mockMessaging verify]; #endif - } #if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 diff --git a/Firebase/Messaging/FIRMessaging.m b/Firebase/Messaging/FIRMessaging.m index 6a9870281f6..6a16840eb61 100644 --- a/Firebase/Messaging/FIRMessaging.m +++ b/Firebase/Messaging/FIRMessaging.m @@ -259,7 +259,7 @@ - (void)configureMessaging:(FIRApp *)app { @"proper integration.", kFIRMessagingRemoteNotificationsProxyEnabledInfoPlistKey, docsURLString); - [FIRMessagingRemoteNotificationsProxy swizzleMethods]; + [[FIRMessagingRemoteNotificationsProxy sharedProxy] swizzleMethodsIfPossible]; } } diff --git a/Firebase/Messaging/FIRMessagingRemoteNotificationsProxy.h b/Firebase/Messaging/FIRMessagingRemoteNotificationsProxy.h index 59c3c15d94d..f0010b3b783 100644 --- a/Firebase/Messaging/FIRMessagingRemoteNotificationsProxy.h +++ b/Firebase/Messaging/FIRMessagingRemoteNotificationsProxy.h @@ -31,10 +31,15 @@ */ + (BOOL)canSwizzleMethods; +/** + * A shared instance of `FIRMessagingRemoteNotificationsProxy` + */ ++ (instancetype)sharedProxy; + /** * Swizzles Application Delegate's remote-notification callbacks and User Notification Center * delegate callback, and invokes the original selectors once done. */ -+ (void)swizzleMethods; +- (void)swizzleMethodsIfPossible; @end From 7861cdeb9155b9dc708703756d1c955f8da010b5 Mon Sep 17 00:00:00 2001 From: Maksym Malyhin Date: Thu, 28 Mar 2019 15:56:04 -0400 Subject: [PATCH 10/24] FIRMessagingRemoteNotificationsProxy test only public API with no implementation details assumptions --- ...FIRMessagingRemoteNotificationsProxyTest.m | 181 +++++++++++------- 1 file changed, 107 insertions(+), 74 deletions(-) diff --git a/Example/Messaging/Tests/FIRMessagingRemoteNotificationsProxyTest.m b/Example/Messaging/Tests/FIRMessagingRemoteNotificationsProxyTest.m index 84618f230ba..cdabea8dd8d 100644 --- a/Example/Messaging/Tests/FIRMessagingRemoteNotificationsProxyTest.m +++ b/Example/Messaging/Tests/FIRMessagingRemoteNotificationsProxyTest.m @@ -24,39 +24,30 @@ #import "FIRMessaging.h" #import "FIRMessagingRemoteNotificationsProxy.h" -#pragma mark - Expose Internal Methods for Testing -// Expose some internal properties and methods here, in order to test -@interface FIRMessagingRemoteNotificationsProxy () - -@property(readonly, nonatomic) BOOL hasSwizzledUserNotificationDelegate; -@property(readonly, nonatomic) BOOL isObservingUserNotificationDelegateChanges; - -@property(strong, readonly, nonatomic) id userNotificationCenter; -@property(strong, readonly, nonatomic) id currentUserNotificationCenterDelegate; - -- (void)listenForDelegateChangesInUserNotificationCenter:(id)notificationCenter; -- (void)swizzleUserNotificationCenterDelegate:(id)delegate; -- (void)unswizzleUserNotificationCenterDelegate:(id)delegate; - -void FCM_swizzle_appDidReceiveRemoteNotificationWithHandler( - id self, SEL _cmd, UIApplication *app, NSDictionary *userInfo, - void (^handler)(UIBackgroundFetchResult)); -void FCM_swizzle_willPresentNotificationWithHandler( - id self, SEL _cmd, id center, id notification, void (^handler)(NSUInteger)); -void FCM_swizzle_didReceiveNotificationResponseWithHandler( - id self, SEL _cmd, id center, id response, void (^handler)()); - -@end - #pragma mark - Invalid App Delegate -@interface InvalidAppDelegate : NSObject +@interface RandomObject : NSObject +@property(nonatomic, weak) id delegate; @end -@implementation InvalidAppDelegate +@implementation RandomObject - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { } + +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 +- (void)userNotificationCenter:(UNUserNotificationCenter *)center + willPresentNotification:(UNNotification *)notification + withCompletionHandler:(void (^)(UNNotificationPresentationOptions options)) +completionHandler { +} + +- (void)userNotificationCenter:(UNUserNotificationCenter *)center +didReceiveNotificationResponse:(UNNotificationResponse *)response + withCompletionHandler:(void(^)(void))completionHandler { +} + +#endif // __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 @end #pragma mark - Incomplete App Delegate @@ -117,11 +108,10 @@ - (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNoti @interface FIRMessagingRemoteNotificationsProxyTest : XCTestCase @property(nonatomic, strong) FIRMessagingRemoteNotificationsProxy *proxy; -@property(nonatomic, strong) id mockProxy; @property(nonatomic, strong) id mockProxyClass; -@property(nonatomic, strong) id mockMessagingClass; @property(nonatomic, strong) id mockSharedApplication; @property(nonatomic, strong) id mockMessaging; +@property(nonatomic, strong) id mockUserNotificationCenter; @end @@ -135,22 +125,20 @@ - (void)setUp { OCMStub([_mockMessaging messaging]).andReturn(_mockMessaging); _proxy = [[FIRMessagingRemoteNotificationsProxy alloc] init]; - _mockProxy = OCMPartialMock(_proxy); _mockProxyClass = OCMClassMock([FIRMessagingRemoteNotificationsProxy class]); // Update +sharedProxy to always return our test instance OCMStub([_mockProxyClass sharedProxy]).andReturn(self.proxy); + +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 + _mockUserNotificationCenter = OCMClassMock([UNUserNotificationCenter class]); + OCMStub([_mockUserNotificationCenter currentNotificationCenter]).andReturn(_mockUserNotificationCenter); +#endif } - (void)tearDown { - [_mockMessagingClass stopMocking]; - _mockMessagingClass = nil; - [_mockProxyClass stopMocking]; _mockProxyClass = nil; - [_mockProxy stopMocking]; - _mockProxy = nil; - [_mockMessaging stopMocking]; _mockMessaging = nil; @@ -164,7 +152,7 @@ - (void)tearDown { #pragma mark - Method Swizzling Tests - (void)testSwizzlingNonAppDelegate { - InvalidAppDelegate *invalidAppDelegate = [[InvalidAppDelegate alloc] init]; + RandomObject *invalidAppDelegate = [[RandomObject alloc] init]; [OCMStub([self.mockSharedApplication delegate]) andReturn:invalidAppDelegate]; [self.proxy swizzleMethodsIfPossible]; @@ -185,6 +173,8 @@ - (void)testSwizzledIncompleteAppDelegateRemoteNotificationMethod { [incompleteAppDelegate application:self.mockSharedApplication didReceiveRemoteNotification:notification]; + + [self.mockMessaging verify]; } - (void)testIncompleteAppDelegateRemoteNotificationWithFetchHandlerMethod { @@ -207,7 +197,7 @@ - (void)testSwizzledAppDelegateRemoteNotificationMethods { NSDictionary *notification = @{@"test" : @""}; - // application:didReceiveRemoteNotification: + //Test application:didReceiveRemoteNotification: // Verify our swizzled method was called OCMExpect([self.mockMessaging appDidReceiveMessage:notification]); @@ -220,7 +210,7 @@ - (void)testSwizzledAppDelegateRemoteNotificationMethods { XCTAssertTrue(appDelegate.remoteNotificationMethodWasCalled); [self.mockMessaging verify]; - // application:didReceiveRemoteNotification:fetchCompletionHandler: + //Test application:didReceiveRemoteNotification:fetchCompletionHandler: // Verify our swizzled method was called OCMExpect([self.mockMessaging appDidReceiveMessage:notification]); @@ -231,6 +221,7 @@ - (void)testSwizzledAppDelegateRemoteNotificationMethods { // Verify our original method was called XCTAssertTrue(appDelegate.remoteNotificationWithFetchHandlerWasCalled); + [self.mockMessaging verify]; #endif } @@ -238,21 +229,50 @@ - (void)testSwizzledAppDelegateRemoteNotificationMethods { #if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 - (void)testListeningForDelegateChangesOnInvalidUserNotificationCenter { - id randomObject = @"Random Object that is not a User Notification Center"; - [self.proxy listenForDelegateChangesInUserNotificationCenter:randomObject]; - XCTAssertFalse(self.proxy.isObservingUserNotificationDelegateChanges); + RandomObject *invalidNotificationCenter = [[RandomObject alloc] init]; + OCMStub([self.mockUserNotificationCenter currentNotificationCenter]).andReturn(invalidNotificationCenter); + [self.proxy swizzleMethodsIfPossible]; + + OCMReject([self.mockMessaging appDidReceiveMessage:[OCMArg any]]); + + [(id)invalidNotificationCenter.delegate + userNotificationCenter:self.mockUserNotificationCenter + willPresentNotification:OCMClassMock([UNNotification class]) + withCompletionHandler:^(UNNotificationPresentationOptions options) { + }]; } - (void)testSwizzlingInvalidUserNotificationCenterDelegate { - id randomObject = @"Random Object that is not a User Notification Center Delegate"; - [self.proxy swizzleUserNotificationCenterDelegate:randomObject]; - XCTAssertFalse(self.proxy.hasSwizzledUserNotificationDelegate); + RandomObject *invalidDelegate = [[RandomObject alloc] init]; + OCMStub([self.mockUserNotificationCenter delegate]).andReturn(invalidDelegate); + [self.proxy swizzleMethodsIfPossible]; + + OCMReject([self.mockMessaging appDidReceiveMessage:[OCMArg any]]); + + [invalidDelegate + userNotificationCenter:self.mockUserNotificationCenter + willPresentNotification:OCMClassMock([UNNotification class]) + withCompletionHandler:^(UNNotificationPresentationOptions options) { + }]; } - (void)testSwizzlingUserNotificationsCenterDelegate { FakeUserNotificationCenterDelegate *delegate = [[FakeUserNotificationCenterDelegate alloc] init]; - [self.proxy swizzleUserNotificationCenterDelegate:delegate]; - XCTAssertTrue(self.proxy.hasSwizzledUserNotificationDelegate); + OCMStub([self.mockUserNotificationCenter delegate]).andReturn(delegate); + [self.proxy swizzleMethodsIfPossible]; + + NSDictionary *message = @{@"message": @""}; + id notification = [self userNotificationWithMessage:message]; + + OCMExpect([self.mockMessaging appDidReceiveMessage:message]); + + [delegate + userNotificationCenter:self.mockUserNotificationCenter + willPresentNotification:notification + withCompletionHandler:^(UNNotificationPresentationOptions options) { + }]; + + [self.mockMessaging verify]; } // Use a fake delegate that doesn't actually implement the needed delegate method. @@ -265,7 +285,8 @@ - (void)testIncompleteUserNotificationCenterDelegateMethod { } IncompleteUserNotificationCenterDelegate *delegate = [[IncompleteUserNotificationCenterDelegate alloc] init]; - [self.mockProxy swizzleUserNotificationCenterDelegate:delegate]; + OCMStub([self.mockUserNotificationCenter delegate]).andReturn(delegate); + [self.proxy swizzleMethodsIfPossible]; // Because the incomplete delete does not implement either of the optional delegate methods, we // should swizzle nothing. If we had swizzled them, then respondsToSelector: would return YES // even though the delegate does not implement the methods. @@ -278,50 +299,51 @@ - (void)testIncompleteUserNotificationCenterDelegateMethod { // Use an object that does actually implement the optional methods. Both should be called. - (void)testSwizzledUserNotificationsCenterDelegate { - // Early exit if running on pre iOS 10 - if (![UNNotification class]) { - return; - } FakeUserNotificationCenterDelegate *delegate = [[FakeUserNotificationCenterDelegate alloc] init]; - [self.mockProxy swizzleUserNotificationCenterDelegate:delegate]; + OCMStub([self.mockUserNotificationCenter delegate]).andReturn(delegate); + [self.proxy swizzleMethodsIfPossible]; + + NSDictionary *message = @{@"message": @""}; + + // Verify userNotificationCenter:willPresentNotification:withCompletionHandler: + OCMExpect([self.mockMessaging appDidReceiveMessage:message]); + // Invoking delegate method should also invoke our swizzled method // The swizzled method uses the +sharedProxy, which should be - // returning our mocked proxy. + // returning our proxy. // Use non-nil, proper classes, otherwise our SDK bails out. - [delegate userNotificationCenter:OCMClassMock([UNUserNotificationCenter class]) - willPresentNotification:[self generateMockNotification] + [delegate userNotificationCenter:self.mockUserNotificationCenter + willPresentNotification:[self userNotificationWithMessage:message] withCompletionHandler:^(NSUInteger options) {}]; - // Verify our swizzled method was called - OCMVerify(FCM_swizzle_willPresentNotificationWithHandler); + // Verify our original method was called XCTAssertTrue(delegate.willPresentWasCalled); + + // Verify our swizzled method was called + [self.mockMessaging verify]; + #if TARGET_OS_IOS - [delegate userNotificationCenter:OCMClassMock([UNUserNotificationCenter class]) - didReceiveNotificationResponse:[self generateMockNotificationResponse] + // Verify userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler: + + OCMExpect([self.mockMessaging appDidReceiveMessage:message]); + + [delegate userNotificationCenter:self.mockUserNotificationCenter + didReceiveNotificationResponse:[self userNotificationResponseWithMessage:message] withCompletionHandler:^{}]; - // Verify our swizzled method was called - OCMVerify(FCM_swizzle_appDidReceiveRemoteNotificationWithHandler); + // Verify our original method was called XCTAssertTrue(delegate.didReceiveResponseWasCalled); -#endif -} -- (id)generateMockNotification { - // Stub out: notification.request.content.userInfo - id mockNotification = OCMClassMock([UNNotification class]); - id mockRequest = OCMClassMock([UNNotificationRequest class]); - id mockContent = OCMClassMock([UNNotificationContent class]); - OCMStub([mockContent userInfo]).andReturn(@{}); - OCMStub([mockRequest content]).andReturn(mockContent); - OCMStub([mockNotification request]).andReturn(mockRequest); - return mockNotification; + // Verify our swizzled method was called + [self.mockMessaging verify]; +#endif // TARGET_OS_IOS } -- (id)generateMockNotificationResponse { +- (id)userNotificationResponseWithMessage:(NSDictionary *)message { // Stub out: response.[mock notification above] #if TARGET_OS_IOS id mockNotificationResponse = OCMClassMock([UNNotificationResponse class]); - id mockNotification = [self generateMockNotification]; + id mockNotification = [self userNotificationWithMessage:message]; OCMStub([mockNotificationResponse notification]).andReturn(mockNotification); return mockNotificationResponse; #else @@ -329,6 +351,17 @@ - (id)generateMockNotificationResponse { #endif } +- (UNNotification *)userNotificationWithMessage:(NSDictionary *)message { + UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init]; + content.userInfo = message; + id notificationRequest = OCMClassMock([UNNotificationRequest class]); + OCMStub([notificationRequest content]).andReturn(content); + id notification = OCMClassMock([UNNotification class]); + OCMStub([notification request]).andReturn(notificationRequest); + + return notification; +} + #endif // __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 @end From b7703ac42221de033ecb49788bc3e1495b78f63c Mon Sep 17 00:00:00 2001 From: Maksym Malyhin Date: Thu, 28 Mar 2019 16:02:48 -0400 Subject: [PATCH 11/24] Cleanup --- .../Tests/FIRMessagingRemoteNotificationsProxyTest.m | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Example/Messaging/Tests/FIRMessagingRemoteNotificationsProxyTest.m b/Example/Messaging/Tests/FIRMessagingRemoteNotificationsProxyTest.m index cdabea8dd8d..19ea3ac3087 100644 --- a/Example/Messaging/Tests/FIRMessagingRemoteNotificationsProxyTest.m +++ b/Example/Messaging/Tests/FIRMessagingRemoteNotificationsProxyTest.m @@ -96,7 +96,9 @@ - (void)userNotificationCenter:(UNUserNotificationCenter *)center self.willPresentWasCalled = YES; } #if TARGET_OS_IOS -- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler { +- (void)userNotificationCenter:(UNUserNotificationCenter *)center +didReceiveNotificationResponse:(UNNotificationResponse *)response + withCompletionHandler:(void (^)(void))completionHandler { self.didReceiveResponseWasCalled = YES; } #endif // TARGET_OS_IOS @@ -346,9 +348,9 @@ - (id)userNotificationResponseWithMessage:(NSDictionary *)message { id mockNotification = [self userNotificationWithMessage:message]; OCMStub([mockNotificationResponse notification]).andReturn(mockNotification); return mockNotificationResponse; -#else +#else // TARGET_OS_IOS return nil; -#endif +#endif // TARGET_OS_IOS } - (UNNotification *)userNotificationWithMessage:(NSDictionary *)message { From ad1da54d0fb34f72fb3139ef833a711fb8d555b1 Mon Sep 17 00:00:00 2001 From: Maksym Malyhin Date: Thu, 28 Mar 2019 16:05:00 -0400 Subject: [PATCH 12/24] Cleanup --- .../Messaging/Tests/FIRMessagingRemoteNotificationsProxyTest.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Example/Messaging/Tests/FIRMessagingRemoteNotificationsProxyTest.m b/Example/Messaging/Tests/FIRMessagingRemoteNotificationsProxyTest.m index 19ea3ac3087..d97bfbeb197 100644 --- a/Example/Messaging/Tests/FIRMessagingRemoteNotificationsProxyTest.m +++ b/Example/Messaging/Tests/FIRMessagingRemoteNotificationsProxyTest.m @@ -24,7 +24,7 @@ #import "FIRMessaging.h" #import "FIRMessagingRemoteNotificationsProxy.h" -#pragma mark - Invalid App Delegate +#pragma mark - Invalid App Delegate or UNNotificationCenter @interface RandomObject : NSObject @property(nonatomic, weak) id delegate; From 86ff257cc507844fb272fae484002fdcb5e4e3af Mon Sep 17 00:00:00 2001 From: Maksym Malyhin Date: Fri, 29 Mar 2019 10:33:24 -0400 Subject: [PATCH 13/24] Cocoapods 1.6.1: Podfile supports only single post_install hook. It is applied to all targets. Move the hook to the top level to avoid confusion. --- Example/Podfile | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Example/Podfile b/Example/Podfile index f8016c949ab..6040bb6a3f2 100644 --- a/Example/Podfile +++ b/Example/Podfile @@ -62,17 +62,6 @@ target 'DynamicLinks_Example_iOS' do pod 'OCMock' pod 'GoogleUtilities/MethodSwizzler', :path => '../' pod 'GoogleUtilities/SwizzlerTestHelpers', :path => '../' - - # Set define to turn on the unswizzler for the unit tests - post_install do |installer_representation| - installer_representation.pods_project.targets.each do |target| - target.build_configurations.each do |config| - if config.name != 'Release' - config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= ['$(inherited)', 'GUL_UNSWIZZLING_ENABLED=1'] - end - end - end - end end end @@ -272,3 +261,14 @@ target 'InstanceID_Example_tvOS' do end end +# Set define to turn on GUL_UNSWIZZLING and GUL_APP_DELEGATE_TESTING for the unit tests +post_install do |installer_representation| + installer_representation.pods_project.targets.each do |target| + target.build_configurations.each do |config| + if config.name != 'Release' + config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= ['$(inherited)', 'GUL_UNSWIZZLING_ENABLED=1', 'GUL_APP_DELEGATE_TESTING=1'] + end + end + end +end + From 79b7613d49f4f025601fa8ac0f4187443c334a1f Mon Sep 17 00:00:00 2001 From: Maksym Malyhin Date: Fri, 29 Mar 2019 10:37:38 -0400 Subject: [PATCH 14/24] FIRMessagingRemoteNotificationsProxyTest: [GULAppDelegateSwizzler resetProxyOriginalDelegateOnceToken] at the beginning of each test. --- Example/Firebase.xcodeproj/project.pbxproj | 1 + .../FIRMessagingRemoteNotificationsProxyTest.m | 15 +++++++++++++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/Example/Firebase.xcodeproj/project.pbxproj b/Example/Firebase.xcodeproj/project.pbxproj index 1e07faddddb..38eaa0d2cb5 100644 --- a/Example/Firebase.xcodeproj/project.pbxproj +++ b/Example/Firebase.xcodeproj/project.pbxproj @@ -7613,6 +7613,7 @@ "$(inherited)", "COCOAPODS=1", "GPB_USE_PROTOBUF_FRAMEWORK_IMPORTS=1", + "GUL_APP_DELEGATE_TESTING=1", ); HEADER_SEARCH_PATHS = ( "$(inherited)", diff --git a/Example/Messaging/Tests/FIRMessagingRemoteNotificationsProxyTest.m b/Example/Messaging/Tests/FIRMessagingRemoteNotificationsProxyTest.m index a46efffc979..7925475e2a9 100644 --- a/Example/Messaging/Tests/FIRMessagingRemoteNotificationsProxyTest.m +++ b/Example/Messaging/Tests/FIRMessagingRemoteNotificationsProxyTest.m @@ -24,6 +24,9 @@ #import "FIRMessaging.h" #import "FIRMessagingRemoteNotificationsProxy.h" +#import +#import + #pragma mark - Invalid App Delegate or UNNotificationCenter @interface RandomObject : NSObject @@ -107,6 +110,10 @@ - (void)userNotificationCenter:(UNUserNotificationCenter *)center @end #endif // __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 +@interface GULAppDelegateSwizzler (FIRMessagingRemoteNotificationsProxyTest) ++ (void)resetProxyOriginalDelegateOnceToken; +@end + #pragma mark - Local, Per-Test Properties @interface FIRMessagingRemoteNotificationsProxyTest : XCTestCase @@ -123,6 +130,10 @@ @implementation FIRMessagingRemoteNotificationsProxyTest - (void)setUp { [super setUp]; + + GULLoggerForceDebug(); + [GULAppDelegateSwizzler resetProxyOriginalDelegateOnceToken]; + _mockSharedApplication = OCMPartialMock([UIApplication sharedApplication]); _mockMessaging = OCMClassMock([FIRMessaging class]); @@ -187,7 +198,7 @@ - (void)testIncompleteAppDelegateRemoteNotificationWithFetchHandlerMethod { [self.proxy swizzleMethodsIfPossible]; SEL remoteNotificationWithFetchHandler = - @selector(application:didReceiveRemoteNotification:fetchCompletionHandler:); + @selector(application:didReceiveRemoteNotification:fetchCompletionHandler:); XCTAssertFalse([incompleteAppDelegate respondsToSelector:remoteNotificationWithFetchHandler]); SEL remoteNotification = @selector(application:didReceiveRemoteNotification:); XCTAssertTrue([incompleteAppDelegate respondsToSelector:remoteNotification]); @@ -219,7 +230,7 @@ - (void)testSwizzledAppDelegateRemoteNotificationMethods { // Verify our swizzled method was called OCMExpect([self.mockMessaging appDidReceiveMessage:notification]); - [appDelegate application:OCMClassMock([UIApplication class]) + [appDelegate application:self.mockSharedApplication didReceiveRemoteNotification:notification fetchCompletionHandler:^(UIBackgroundFetchResult result) {}]; From 98ffccbbd08514b499f5c4f4d9cf0faff29ce65e Mon Sep 17 00:00:00 2001 From: Maksym Malyhin Date: Fri, 29 Mar 2019 11:31:08 -0400 Subject: [PATCH 15/24] FIRMessagingRemoteNotificationsProxy: app delegate missing methods tests added. Cleanup --- ...FIRMessagingRemoteNotificationsProxyTest.m | 39 ++++++- .../FIRMessagingRemoteNotificationsProxy.m | 100 ------------------ 2 files changed, 37 insertions(+), 102 deletions(-) diff --git a/Example/Messaging/Tests/FIRMessagingRemoteNotificationsProxyTest.m b/Example/Messaging/Tests/FIRMessagingRemoteNotificationsProxyTest.m index 7925475e2a9..0727ac29a6b 100644 --- a/Example/Messaging/Tests/FIRMessagingRemoteNotificationsProxyTest.m +++ b/Example/Messaging/Tests/FIRMessagingRemoteNotificationsProxyTest.m @@ -65,6 +65,8 @@ @implementation IncompleteAppDelegate @interface FakeAppDelegate : NSObject @property(nonatomic) BOOL remoteNotificationMethodWasCalled; @property(nonatomic) BOOL remoteNotificationWithFetchHandlerWasCalled; +@property(nonatomic, strong) NSData *deviceToken; +@property(nonatomic, strong) NSError *registerForRemoteNotificationsError; @end @implementation FakeAppDelegate #if TARGET_OS_IOS @@ -78,6 +80,17 @@ - (void)application:(UIApplication *)application fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { self.remoteNotificationWithFetchHandlerWasCalled = YES; } + +- (void)application:(UIApplication *)application + didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { + self.deviceToken = deviceToken; +} + +- (void)application:(UIApplication *)application + didFailToRegisterForRemoteNotificationsWithError:(NSError *)error { + self.registerForRemoteNotificationsError = error; +} + @end #pragma mark - Incompete UNUserNotificationCenterDelegate @@ -145,8 +158,7 @@ - (void)setUp { OCMStub([_mockProxyClass sharedProxy]).andReturn(self.proxy); #if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 - _mockUserNotificationCenter = OCMClassMock([UNUserNotificationCenter class]); - OCMStub([_mockUserNotificationCenter currentNotificationCenter]).andReturn(_mockUserNotificationCenter); + _mockUserNotificationCenter = OCMPartialMock([UNUserNotificationCenter currentNotificationCenter]); #endif } @@ -160,6 +172,9 @@ - (void)tearDown { [_mockSharedApplication stopMocking]; _mockSharedApplication = nil; + [_mockUserNotificationCenter stopMocking]; + _mockUserNotificationCenter = nil; + _proxy = nil; [super tearDown]; } @@ -238,6 +253,26 @@ - (void)testSwizzledAppDelegateRemoteNotificationMethods { XCTAssertTrue(appDelegate.remoteNotificationWithFetchHandlerWasCalled); [self.mockMessaging verify]; + + // Verify application:didRegisterForRemoteNotificationsWithDeviceToken: + NSData *deviceToken = [NSData data]; + + OCMExpect([self.mockMessaging setAPNSToken:deviceToken]); + + [appDelegate application:self.mockSharedApplication + didRegisterForRemoteNotificationsWithDeviceToken:deviceToken]; + + XCTAssertEqual(appDelegate.deviceToken, deviceToken); + [self.mockMessaging verify]; + + // Verify application:didFailToRegisterForRemoteNotificationsWithError: + NSError *error = [NSError errorWithDomain:@"tests" code:-1 userInfo:nil]; + + [appDelegate application:self.mockSharedApplication + didFailToRegisterForRemoteNotificationsWithError:error]; + + XCTAssertEqual(appDelegate.registerForRemoteNotificationsError, error); + #endif } diff --git a/Firebase/Messaging/FIRMessagingRemoteNotificationsProxy.m b/Firebase/Messaging/FIRMessagingRemoteNotificationsProxy.m index dea289a4d9f..b7fc9c952f4 100644 --- a/Firebase/Messaging/FIRMessagingRemoteNotificationsProxy.m +++ b/Firebase/Messaging/FIRMessagingRemoteNotificationsProxy.m @@ -31,7 +31,6 @@ @"userNotificationCenter:willPresentNotification:withCompletionHandler:"; static NSString *kUserNotificationDidReceiveResponseSelectorString = @"userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:"; -static NSString *kReceiveDataMessageSelectorString = @"messaging:didReceiveMessage:"; @interface FIRMessagingRemoteNotificationsProxy () @@ -39,7 +38,6 @@ @interface FIRMessagingRemoteNotificationsProxy () @property(strong, nonatomic) NSMutableDictionary *swizzledSelectorsByClass; @property(nonatomic) BOOL didSwizzleMethods; -@property(nonatomic) BOOL didSwizzleAppDelegateMethods; @property(nonatomic) BOOL hasSwizzledUserNotificationDelegate; @property(nonatomic) BOOL isObservingUserNotificationDelegateChanges; @@ -92,13 +90,6 @@ - (void)swizzleMethodsIfPossible { [GULAppDelegateSwizzler proxyOriginalDelegate]; self.appDelegateInterceptorID = [GULAppDelegateSwizzler registerAppDelegateInterceptor:self]; - UIApplication *application = [GULAppDelegateSwizzler sharedApplication]; - if (!application) { - return; - } - NSObject *appDelegate = [application delegate]; - [self swizzleAppDelegateMethods:appDelegate]; - // Add KVO listener on [UNUserNotificationCenter currentNotificationCenter]'s delegate property Class notificationCenterClass = NSClassFromString(@"UNUserNotificationCenter"); if (notificationCenterClass) { @@ -127,24 +118,6 @@ - (void)unswizzleAllMethods { [self.swizzledSelectorsByClass removeAllObjects]; } -- (void)swizzleAppDelegateMethods:(id)appDelegate { - Class appDelegateClass = [appDelegate class]; - - BOOL didSwizzleAppDelegate = NO; - - // For data message from MCS. - SEL receiveDataMessageSelector = NSSelectorFromString(kReceiveDataMessageSelectorString); - if ([appDelegate respondsToSelector:receiveDataMessageSelector]) { - [self swizzleSelector:receiveDataMessageSelector - inClass:appDelegateClass - withImplementation:(IMP)FCM_swizzle_messagingDidReceiveMessage - inProtocol:@protocol(UIApplicationDelegate)]; - didSwizzleAppDelegate = YES; - } - - self.didSwizzleAppDelegateMethods = didSwizzleAppDelegate; -} - - (void)listenForDelegateChangesInUserNotificationCenter:(id)notificationCenter { Class notificationCenterClass = NSClassFromString(@"UNUserNotificationCenter"); if (![notificationCenter isKindOfClass:notificationCenterClass]) { @@ -446,37 +419,6 @@ - (void)application:(UIApplication *)application #pragma mark - Swizzled Methods -void FCM_swizzle_appDidReceiveRemoteNotification(id self, - SEL _cmd, - UIApplication *app, - NSDictionary *userInfo) { - [[FIRMessaging messaging] appDidReceiveMessage:userInfo]; - - IMP original_imp = - [[FIRMessagingRemoteNotificationsProxy sharedProxy] originalImplementationForSelector:_cmd]; - if (original_imp) { - ((void (*)(id, SEL, UIApplication *, NSDictionary *))original_imp)(self, - _cmd, - app, - userInfo); - } -} - -void FCM_swizzle_appDidReceiveRemoteNotificationWithHandler( - id self, SEL _cmd, UIApplication *app, NSDictionary *userInfo, - void (^handler)(UIBackgroundFetchResult)) { - - [[FIRMessaging messaging] appDidReceiveMessage:userInfo]; - - IMP original_imp = - [[FIRMessagingRemoteNotificationsProxy sharedProxy] originalImplementationForSelector:_cmd]; - if (original_imp) { - ((void (*)(id, SEL, UIApplication *, NSDictionary *, - void (^)(UIBackgroundFetchResult)))original_imp)( - self, _cmd, app, userInfo, handler); - } -} - /** * Swizzle the notification handler for iOS 10+ devices. * Signature of original handler is as below: @@ -652,46 +594,4 @@ id userInfoFromNotification(id notification) { return notificationUserInfo; } -void FCM_swizzle_messagingDidReceiveMessage(id self, SEL _cmd, FIRMessaging *message, - FIRMessagingRemoteMessage *remoteMessage) { - [[FIRMessaging messaging] appDidReceiveMessage:remoteMessage.appData]; - - IMP original_imp = - [[FIRMessagingRemoteNotificationsProxy sharedProxy] originalImplementationForSelector:_cmd]; - if (original_imp) { - ((void (*)(id, SEL, FIRMessaging *, FIRMessagingRemoteMessage *))original_imp)( - self, _cmd, message, remoteMessage); - } -} - -void FCM_swizzle_appDidFailToRegisterForRemoteNotifications(id self, - SEL _cmd, - UIApplication *app, - NSError *error) { - // Log the fact that we failed to register for remote notifications - FIRMessagingLoggerError(kFIRMessagingMessageCodeRemoteNotificationsProxyAPNSFailed, - @"Error in " - @"application:didFailToRegisterForRemoteNotificationsWithError: %@", - error.localizedDescription); - IMP original_imp = - [[FIRMessagingRemoteNotificationsProxy sharedProxy] originalImplementationForSelector:_cmd]; - if (original_imp) { - ((void (*)(id, SEL, UIApplication *, NSError *))original_imp)(self, _cmd, app, error); - } -} - -void FCM_swizzle_appDidRegisterForRemoteNotifications(id self, - SEL _cmd, - UIApplication *app, - NSData *deviceToken) { - // Pass the APNSToken along to FIRMessaging (and auto-detect the token type) - [FIRMessaging messaging].APNSToken = deviceToken; - - IMP original_imp = - [[FIRMessagingRemoteNotificationsProxy sharedProxy] originalImplementationForSelector:_cmd]; - if (original_imp) { - ((void (*)(id, SEL, UIApplication *, NSData *))original_imp)(self, _cmd, app, deviceToken); - } -} - @end From cd4439540e53bf3192f1182b8c4c1e98460beded Mon Sep 17 00:00:00 2001 From: Maksym Malyhin Date: Fri, 29 Mar 2019 15:06:56 -0400 Subject: [PATCH 16/24] GULAppDelegateSwizzler: don't swizzle invalid application delegate --- .../AppDelegateSwizzler/GULAppDelegateSwizzler.m | 9 +++++++++ GoogleUtilities/Common/GULLoggerCodes.h | 1 + .../Example/Tests/Swizzler/GULAppDelegateSwizzlerTest.m | 6 ++++++ 3 files changed, 16 insertions(+) diff --git a/GoogleUtilities/AppDelegateSwizzler/GULAppDelegateSwizzler.m b/GoogleUtilities/AppDelegateSwizzler/GULAppDelegateSwizzler.m index 827f8c3f71d..b789c708307 100644 --- a/GoogleUtilities/AppDelegateSwizzler/GULAppDelegateSwizzler.m +++ b/GoogleUtilities/AppDelegateSwizzler/GULAppDelegateSwizzler.m @@ -834,6 +834,15 @@ - (void)application:(UIApplication *)application } + (void)proxyAppDelegate:(id)appDelegate { + if (![appDelegate conformsToProtocol:@protocol(UIApplicationDelegate)]) { + GULLogNotice(kGULLoggerSwizzler, NO, + [NSString stringWithFormat:@"I-SWZ%06ld", + (long)kGULSwizzlerMessageCodeAppDelegateSwizzlingInvalidAppDelegate], + @"App Delegate does not conform to UIApplicationDelegate protocol. %@", + [GULAppDelegateSwizzler correctAlternativeWhenAppDelegateProxyNotCreated]); + return; + } + id originalDelegate = appDelegate; // Do not create a subclass if it is not enabled. if (![GULAppDelegateSwizzler isAppDelegateProxyEnabled]) { diff --git a/GoogleUtilities/Common/GULLoggerCodes.h b/GoogleUtilities/Common/GULLoggerCodes.h index b71c03797cd..8cae326f3e1 100644 --- a/GoogleUtilities/Common/GULLoggerCodes.h +++ b/GoogleUtilities/Common/GULLoggerCodes.h @@ -30,6 +30,7 @@ typedef NS_ENUM(NSInteger, GULSwizzlerMessageCode) { kGULSwizzlerMessageCodeAppDelegateSwizzling011 = 1011, // I-SWZ001011 kGULSwizzlerMessageCodeAppDelegateSwizzling012 = 1012, // I-SWZ001012 kGULSwizzlerMessageCodeAppDelegateSwizzling013 = 1013, // I-SWZ001013 + kGULSwizzlerMessageCodeAppDelegateSwizzlingInvalidAppDelegate = 1014, // I-SWZ001014 // Method Swizzling. kGULSwizzlerMessageCodeMethodSwizzling000 = 2000, // I-SWZ002000 diff --git a/GoogleUtilities/Example/Tests/Swizzler/GULAppDelegateSwizzlerTest.m b/GoogleUtilities/Example/Tests/Swizzler/GULAppDelegateSwizzlerTest.m index 2ad47e4c786..17c990a4ef8 100644 --- a/GoogleUtilities/Example/Tests/Swizzler/GULAppDelegateSwizzlerTest.m +++ b/GoogleUtilities/Example/Tests/Swizzler/GULAppDelegateSwizzlerTest.m @@ -240,6 +240,12 @@ - (void)tearDown { [super tearDown]; } +- (void)testNotAppDelegateIsNotSwizzled { + NSObject *notAppDelegate = [[NSObject alloc] init]; + [GULAppDelegateSwizzler proxyAppDelegate:(id)notAppDelegate]; + XCTAssertEqualObjects(NSStringFromClass([notAppDelegate class]), @"NSObject"); +} + /** Tests proxying an object that responds to UIApplicationDelegate protocol and makes sure that * it is isa swizzled and that the object after proxying responds to the expected methods * and doesn't have its ivars modified. From 3df4140f73feea37d5be0541653d5af7bcaee698 Mon Sep 17 00:00:00 2001 From: Maksym Malyhin Date: Fri, 29 Mar 2019 16:30:21 -0400 Subject: [PATCH 17/24] Cleanup --- .../FIRMessagingApplicationDelegateProxy.h | 27 --------- .../FIRMessagingApplicationDelegateProxy.m | 60 ------------------- 2 files changed, 87 deletions(-) delete mode 100644 Firebase/Messaging/FIRMessagingApplicationDelegateProxy.h delete mode 100644 Firebase/Messaging/FIRMessagingApplicationDelegateProxy.m diff --git a/Firebase/Messaging/FIRMessagingApplicationDelegateProxy.h b/Firebase/Messaging/FIRMessagingApplicationDelegateProxy.h deleted file mode 100644 index 5aa6576ff3e..00000000000 --- a/Firebase/Messaging/FIRMessagingApplicationDelegateProxy.h +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright 2018 Google - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface FIRMessagingApplicationDelegateProxy : NSObject - -+ (instancetype)shared; - -@end - -NS_ASSUME_NONNULL_END diff --git a/Firebase/Messaging/FIRMessagingApplicationDelegateProxy.m b/Firebase/Messaging/FIRMessagingApplicationDelegateProxy.m deleted file mode 100644 index 3c6aa2321e0..00000000000 --- a/Firebase/Messaging/FIRMessagingApplicationDelegateProxy.m +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2018 Google - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import "FIRMessagingApplicationDelegateProxy.h" - -#import "FIRMessagingLogger.h" -#import "FIRMessaging_Private.h" - -@implementation FIRMessagingApplicationDelegateProxy - -+ (instancetype)shared { - static FIRMessagingApplicationDelegateProxy *sharedInstance = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - sharedInstance = [[FIRMessagingApplicationDelegateProxy alloc] init]; - }); - - return sharedInstance; -} - -- (void)application:(UIApplication *)application -didReceiveRemoteNotification:(NSDictionary *)userInfo { - [[FIRMessaging messaging] appDidReceiveMessage:userInfo]; -} - -- (void)application:(UIApplication *)application -didReceiveRemoteNotification:(NSDictionary *)userInfo -fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { - [[FIRMessaging messaging] appDidReceiveMessage:userInfo]; -} - -- (void)application:(UIApplication *)application -didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { - // Pass the APNSToken along to FIRMessaging (and auto-detect the token type) - [FIRMessaging messaging].APNSToken = deviceToken; -} - -- (void)application:(UIApplication *)application -didFailToRegisterForRemoteNotificationsWithError:(NSError *)error { - // Log the fact that we failed to register for remote notifications - FIRMessagingLoggerError(kFIRMessagingMessageCodeRemoteNotificationsProxyAPNSFailed, - @"Error in " - @"application:didFailToRegisterForRemoteNotificationsWithError: %@", - error.localizedDescription); -} - -@end From dcd0e10a23503fc6ed567a34502587e4b854b73e Mon Sep 17 00:00:00 2001 From: Maksym Malyhin Date: Fri, 29 Mar 2019 16:55:19 -0400 Subject: [PATCH 18/24] Run ./scripts/check.sh --- .../GULAppDelegateSwizzler.m | 12 ++++---- GoogleUtilities/Common/GULLoggerCodes.h | 28 +++++++++---------- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/GoogleUtilities/AppDelegateSwizzler/GULAppDelegateSwizzler.m b/GoogleUtilities/AppDelegateSwizzler/GULAppDelegateSwizzler.m index b789c708307..cccfb836d9b 100644 --- a/GoogleUtilities/AppDelegateSwizzler/GULAppDelegateSwizzler.m +++ b/GoogleUtilities/AppDelegateSwizzler/GULAppDelegateSwizzler.m @@ -835,11 +835,13 @@ - (void)application:(UIApplication *)application + (void)proxyAppDelegate:(id)appDelegate { if (![appDelegate conformsToProtocol:@protocol(UIApplicationDelegate)]) { - GULLogNotice(kGULLoggerSwizzler, NO, - [NSString stringWithFormat:@"I-SWZ%06ld", - (long)kGULSwizzlerMessageCodeAppDelegateSwizzlingInvalidAppDelegate], - @"App Delegate does not conform to UIApplicationDelegate protocol. %@", - [GULAppDelegateSwizzler correctAlternativeWhenAppDelegateProxyNotCreated]); + GULLogNotice( + kGULLoggerSwizzler, NO, + [NSString + stringWithFormat:@"I-SWZ%06ld", + (long)kGULSwizzlerMessageCodeAppDelegateSwizzlingInvalidAppDelegate], + @"App Delegate does not conform to UIApplicationDelegate protocol. %@", + [GULAppDelegateSwizzler correctAlternativeWhenAppDelegateProxyNotCreated]); return; } diff --git a/GoogleUtilities/Common/GULLoggerCodes.h b/GoogleUtilities/Common/GULLoggerCodes.h index 8cae326f3e1..fd22ba637a2 100644 --- a/GoogleUtilities/Common/GULLoggerCodes.h +++ b/GoogleUtilities/Common/GULLoggerCodes.h @@ -16,20 +16,20 @@ typedef NS_ENUM(NSInteger, GULSwizzlerMessageCode) { // App Delegate Swizzling. - kGULSwizzlerMessageCodeAppDelegateSwizzling000 = 1000, // I-SWZ001000 - kGULSwizzlerMessageCodeAppDelegateSwizzling001 = 1001, // I-SWZ001001 - kGULSwizzlerMessageCodeAppDelegateSwizzling002 = 1002, // I-SWZ001002 - kGULSwizzlerMessageCodeAppDelegateSwizzling003 = 1003, // I-SWZ001003 - kGULSwizzlerMessageCodeAppDelegateSwizzling004 = 1004, // I-SWZ001004 - kGULSwizzlerMessageCodeAppDelegateSwizzling005 = 1005, // I-SWZ001005 - kGULSwizzlerMessageCodeAppDelegateSwizzling006 = 1006, // I-SWZ001006 - kGULSwizzlerMessageCodeAppDelegateSwizzling007 = 1007, // I-SWZ001007 - kGULSwizzlerMessageCodeAppDelegateSwizzling008 = 1008, // I-SWZ001008 - kGULSwizzlerMessageCodeAppDelegateSwizzling009 = 1009, // I-SWZ001009 - kGULSwizzlerMessageCodeAppDelegateSwizzling010 = 1010, // I-SWZ001010 - kGULSwizzlerMessageCodeAppDelegateSwizzling011 = 1011, // I-SWZ001011 - kGULSwizzlerMessageCodeAppDelegateSwizzling012 = 1012, // I-SWZ001012 - kGULSwizzlerMessageCodeAppDelegateSwizzling013 = 1013, // I-SWZ001013 + kGULSwizzlerMessageCodeAppDelegateSwizzling000 = 1000, // I-SWZ001000 + kGULSwizzlerMessageCodeAppDelegateSwizzling001 = 1001, // I-SWZ001001 + kGULSwizzlerMessageCodeAppDelegateSwizzling002 = 1002, // I-SWZ001002 + kGULSwizzlerMessageCodeAppDelegateSwizzling003 = 1003, // I-SWZ001003 + kGULSwizzlerMessageCodeAppDelegateSwizzling004 = 1004, // I-SWZ001004 + kGULSwizzlerMessageCodeAppDelegateSwizzling005 = 1005, // I-SWZ001005 + kGULSwizzlerMessageCodeAppDelegateSwizzling006 = 1006, // I-SWZ001006 + kGULSwizzlerMessageCodeAppDelegateSwizzling007 = 1007, // I-SWZ001007 + kGULSwizzlerMessageCodeAppDelegateSwizzling008 = 1008, // I-SWZ001008 + kGULSwizzlerMessageCodeAppDelegateSwizzling009 = 1009, // I-SWZ001009 + kGULSwizzlerMessageCodeAppDelegateSwizzling010 = 1010, // I-SWZ001010 + kGULSwizzlerMessageCodeAppDelegateSwizzling011 = 1011, // I-SWZ001011 + kGULSwizzlerMessageCodeAppDelegateSwizzling012 = 1012, // I-SWZ001012 + kGULSwizzlerMessageCodeAppDelegateSwizzling013 = 1013, // I-SWZ001013 kGULSwizzlerMessageCodeAppDelegateSwizzlingInvalidAppDelegate = 1014, // I-SWZ001014 // Method Swizzling. From dae75d905c869f8e11476fdca9487b9ba99d58c7 Mon Sep 17 00:00:00 2001 From: Maksym Malyhin Date: Mon, 1 Apr 2019 11:42:35 -0400 Subject: [PATCH 19/24] GoogleUtilities/GULAppDelegateSwizzler - tvOS support --- ...FIRMessagingRemoteNotificationsProxyTest.m | 5 ++ .../FIRMessagingRemoteNotificationsProxy.m | 7 ++- .../GULAppDelegateSwizzler.m | 59 +++++++++++++------ .../GoogleUtilities.xcodeproj/project.pbxproj | 6 +- GoogleUtilities/Example/Podfile | 20 +++---- .../Swizzler/GULAppDelegateSwizzlerTest.m | 17 +++++- 6 files changed, 81 insertions(+), 33 deletions(-) diff --git a/Example/Messaging/Tests/FIRMessagingRemoteNotificationsProxyTest.m b/Example/Messaging/Tests/FIRMessagingRemoteNotificationsProxyTest.m index 0727ac29a6b..4988c03a297 100644 --- a/Example/Messaging/Tests/FIRMessagingRemoteNotificationsProxyTest.m +++ b/Example/Messaging/Tests/FIRMessagingRemoteNotificationsProxyTest.m @@ -194,6 +194,7 @@ - (void)testSwizzlingNonAppDelegate { } - (void)testSwizzledIncompleteAppDelegateRemoteNotificationMethod { +#if TARGET_OS_IOS IncompleteAppDelegate *incompleteAppDelegate = [[IncompleteAppDelegate alloc] init]; [OCMStub([self.mockSharedApplication delegate]) andReturn:incompleteAppDelegate]; [self.proxy swizzleMethodsIfPossible]; @@ -205,6 +206,7 @@ - (void)testSwizzledIncompleteAppDelegateRemoteNotificationMethod { didReceiveRemoteNotification:notification]; [self.mockMessaging verify]; +#endif // TARGET_OS_IOS } - (void)testIncompleteAppDelegateRemoteNotificationWithFetchHandlerMethod { @@ -215,8 +217,11 @@ - (void)testIncompleteAppDelegateRemoteNotificationWithFetchHandlerMethod { SEL remoteNotificationWithFetchHandler = @selector(application:didReceiveRemoteNotification:fetchCompletionHandler:); XCTAssertFalse([incompleteAppDelegate respondsToSelector:remoteNotificationWithFetchHandler]); + +#if TARGET_OS_IOS SEL remoteNotification = @selector(application:didReceiveRemoteNotification:); XCTAssertTrue([incompleteAppDelegate respondsToSelector:remoteNotification]); +#endif // TARGET_OS_IOS } - (void)testSwizzledAppDelegateRemoteNotificationMethods { diff --git a/Firebase/Messaging/FIRMessagingRemoteNotificationsProxy.m b/Firebase/Messaging/FIRMessagingRemoteNotificationsProxy.m index b7fc9c952f4..c202e092bc5 100644 --- a/Firebase/Messaging/FIRMessagingRemoteNotificationsProxy.m +++ b/Firebase/Messaging/FIRMessagingRemoteNotificationsProxy.m @@ -106,7 +106,10 @@ - (void)swizzleMethodsIfPossible { } - (void)unswizzleAllMethods { - [GULAppDelegateSwizzler unregisterAppDelegateInterceptorWithID:self.appDelegateInterceptorID]; + if (self.appDelegateInterceptorID) { + [GULAppDelegateSwizzler unregisterAppDelegateInterceptorWithID:self.appDelegateInterceptorID]; + } + for (NSString *className in self.swizzledSelectorsByClass) { Class klass = NSClassFromString(className); NSArray *selectorStrings = self.swizzledSelectorsByClass[className]; @@ -391,10 +394,12 @@ id getNamedPropertyFromObject(id object, NSString *propertyName, Class klass) { #pragma mark - UIApplicationDelegate +#if TARGET_OS_IOS - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo { [[FIRMessaging messaging] appDidReceiveMessage:userInfo]; } +#endif // TARGET_OS_IOS - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo diff --git a/GoogleUtilities/AppDelegateSwizzler/GULAppDelegateSwizzler.m b/GoogleUtilities/AppDelegateSwizzler/GULAppDelegateSwizzler.m index cccfb836d9b..e0bf2a05552 100644 --- a/GoogleUtilities/AppDelegateSwizzler/GULAppDelegateSwizzler.m +++ b/GoogleUtilities/AppDelegateSwizzler/GULAppDelegateSwizzler.m @@ -14,7 +14,7 @@ #import "TargetConditionals.h" -#if TARGET_OS_IOS +#if TARGET_OS_IOS || TARGET_OS_TV #import #import @@ -65,14 +65,19 @@ typedef void (*GULRealDidReceiveRemoteNotificationWithCompletionIMP)( static char const *const kGULContinueUserActivityIMPKey = "GUL_continueUserActivityIMP"; static char const *const kGULHandleBackgroundSessionIMPKey = "GUL_handleBackgroundSessionIMP"; static char const *const kGULOpenURLOptionsIMPKey = "GUL_openURLOptionsIMP"; + +#if TARGET_OS_IOS static char const *const kGULOpenURLOptionsSourceAnnotationsIMPKey = "GUL_openURLSourceApplicationAnnotationIMP"; +static char const *const kGULRealDidReceiveRemoteNotificationIMPKey = + "GUL_didReceiveRemoteNotificationIMP"; +#endif // TARGET_OS_IOS + static char const *const kGULRealDidRegisterForRemoteNotificationsIMPKey = "GUL_didRegisterForRemoteNotificationsIMP"; static char const *const kGULRealDidFailToRegisterForRemoteNotificationsIMPKey = "GUL_didFailToRegisterForRemoteNotificationsIMP"; -static char const *const kGULRealDidReceiveRemoteNotificationIMPKey = - "GUL_didReceiveRemoteNotificationIMP"; + static char const *const kGULRealDidReceiveRemoteNotificationWithCompletionIMPKey = "GUL_didReceiveRemoteNotificationWithCompletionIMP"; @@ -354,6 +359,7 @@ + (void)createSubclassWithObject:(id)anObject { fromClass:realClass]; NSValue *continueUserActivityIMPPointer = [NSValue valueWithPointer:continueUserActivityIMP]; +#if TARGET_OS_IOS // For application:openURL:sourceApplication:annotation: SEL openURLSourceApplicationAnnotationSEL = @selector(application: openURL:sourceApplication:annotation:); @@ -367,6 +373,19 @@ + (void)createSubclassWithObject:(id)anObject { NSValue *openURLSourceAppAnnotationIMPPointer = [NSValue valueWithPointer:openURLSourceApplicationAnnotationIMP]; + // For application:didReceiveRemoteNotification: + SEL didReceiveRemoteNotificationSEL = @selector(application:didReceiveRemoteNotification:); + [GULAppDelegateSwizzler addInstanceMethodWithSelector:didReceiveRemoteNotificationSEL + fromClass:[GULAppDelegateSwizzler class] + toClass:appDelegateSubClass]; + GULRealDidReceiveRemoteNotificationIMP didReceiveRemoteNotificationIMP = + (GULRealDidReceiveRemoteNotificationIMP) + [GULAppDelegateSwizzler implementationOfMethodSelector:didReceiveRemoteNotificationSEL + fromClass:realClass]; + NSValue *didReceiveRemoteNotificationIMPPointer = + [NSValue valueWithPointer:didReceiveRemoteNotificationIMP]; +#endif // TARGET_OS_IOS + // For application:handleEventsForBackgroundURLSession:completionHandler: SEL handleEventsForBackgroundURLSessionSEL = @selector(application: handleEventsForBackgroundURLSession:completionHandler:); @@ -430,18 +449,6 @@ + (void)createSubclassWithObject:(id)anObject { [NSValue valueWithPointer:didReceiveRemoteNotificationWithCompletionIMP]; } - // For application:didReceiveRemoteNotification: - SEL didReceiveRemoteNotificationSEL = @selector(application:didReceiveRemoteNotification:); - [GULAppDelegateSwizzler addInstanceMethodWithSelector:didReceiveRemoteNotificationSEL - fromClass:[GULAppDelegateSwizzler class] - toClass:appDelegateSubClass]; - GULRealDidReceiveRemoteNotificationIMP didReceiveRemoteNotificationIMP = - (GULRealDidReceiveRemoteNotificationIMP) - [GULAppDelegateSwizzler implementationOfMethodSelector:didReceiveRemoteNotificationSEL - fromClass:realClass]; - NSValue *didReceiveRemoteNotificationIMPPointer = - [NSValue valueWithPointer:didReceiveRemoteNotificationIMP]; - // Override the description too so the custom class name will not show up. [GULAppDelegateSwizzler addInstanceMethodWithDestinationSelector:@selector(description) withImplementationFromSourceSelector:@selector(fakeDescription) @@ -457,8 +464,16 @@ + (void)createSubclassWithObject:(id)anObject { objc_setAssociatedObject(anObject, &kGULOpenURLOptionsIMPKey, openURLOptionsIMPPointer, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } + +#if TARGET_OS_IOS objc_setAssociatedObject(anObject, &kGULOpenURLOptionsSourceAnnotationsIMPKey, openURLSourceAppAnnotationIMPPointer, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + + objc_setAssociatedObject(anObject, &kGULRealDidReceiveRemoteNotificationIMPKey, + didReceiveRemoteNotificationIMPPointer, + OBJC_ASSOCIATION_RETAIN_NONATOMIC); +#endif // TARGET_OS_IOS + objc_setAssociatedObject(anObject, &kGULRealDidRegisterForRemoteNotificationsIMPKey, didRegisterForRemoteNotificationsIMPPointer, OBJC_ASSOCIATION_RETAIN_NONATOMIC); @@ -468,9 +483,7 @@ + (void)createSubclassWithObject:(id)anObject { objc_setAssociatedObject(anObject, &kGULRealDidReceiveRemoteNotificationWithCompletionIMPKey, didReceiveRemoteNotificationWithCompletionIMPPointer, OBJC_ASSOCIATION_RETAIN_NONATOMIC); - objc_setAssociatedObject(anObject, &kGULRealDidReceiveRemoteNotificationIMPKey, - didReceiveRemoteNotificationIMPPointer, - OBJC_ASSOCIATION_RETAIN_NONATOMIC); + objc_setAssociatedObject(anObject, &kGULRealClassKey, realClass, OBJC_ASSOCIATION_RETAIN_NONATOMIC); @@ -652,6 +665,8 @@ - (BOOL)application:(UIApplication *)application return returnedValue; } +#if TARGET_OS_IOS + - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication @@ -682,6 +697,8 @@ - (BOOL)application:(UIApplication *)application return returnedValue; } +#endif // TARGET_OS_IOS + #pragma mark - [Donor Methods] Network overridden handler methods #pragma clang diagnostic push @@ -812,6 +829,8 @@ - (void)application:(UIApplication *)application } } +#if TARGET_OS_IOS + - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo { NSValue *didReceiveRemoteNotificationIMPPointer = @@ -833,6 +852,8 @@ - (void)application:(UIApplication *)application } } +#endif // TARGET_OS_IOS + + (void)proxyAppDelegate:(id)appDelegate { if (![appDelegate conformsToProtocol:@protocol(UIApplicationDelegate)]) { GULLogNotice( @@ -911,4 +932,4 @@ + (void)resetProxyOriginalDelegateOnceToken { @end -#endif // TARGET_OS_IOS +#endif // TARGET_OS_IOS || TARGET_OS_TV diff --git a/GoogleUtilities/Example/GoogleUtilities.xcodeproj/project.pbxproj b/GoogleUtilities/Example/GoogleUtilities.xcodeproj/project.pbxproj index 08778f3f85c..30940fb98ea 100644 --- a/GoogleUtilities/Example/GoogleUtilities.xcodeproj/project.pbxproj +++ b/GoogleUtilities/Example/GoogleUtilities.xcodeproj/project.pbxproj @@ -13,6 +13,7 @@ 6003F5B0195388D20070C39A /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F5AF195388D20070C39A /* XCTest.framework */; }; 6003F5B1195388D20070C39A /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F58D195388D20070C39A /* Foundation.framework */; }; 6003F5B2195388D20070C39A /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F591195388D20070C39A /* UIKit.framework */; }; + 9A414672225259F900B08D77 /* GULAppDelegateSwizzlerTest.m in Sources */ = {isa = PBXBuildFile; fileRef = EFBE67F92101401100E756A7 /* GULAppDelegateSwizzlerTest.m */; }; 9A7C37C2224BD9C600033B0D /* GULAppDelegateSwizzlerTest.m in Sources */ = {isa = PBXBuildFile; fileRef = EFBE67F92101401100E756A7 /* GULAppDelegateSwizzlerTest.m */; }; DE5CF98E20F686310063FFDD /* GULAppEnvironmentUtilTest.m in Sources */ = {isa = PBXBuildFile; fileRef = DE5CF98C20F686290063FFDD /* GULAppEnvironmentUtilTest.m */; }; DE84BBC421D7EC900048A176 /* GULUserDefaultsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE84BBC321D7EC900048A176 /* GULUserDefaultsTests.m */; }; @@ -93,7 +94,7 @@ 6003F5AE195388D20070C39A /* Tests_iOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Tests_iOS.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 6003F5AF195388D20070C39A /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; 6003F5B7195388D20070C39A /* Tests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Tests-Info.plist"; sourceTree = ""; }; - 7BEA793625C8DE7C8EC60006 /* GoogleUtilities.podspec */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = GoogleUtilities.podspec; path = ../GoogleUtilities.podspec; sourceTree = ""; }; + 7BEA793625C8DE7C8EC60006 /* GoogleUtilities.podspec */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = GoogleUtilities.podspec; path = ../../GoogleUtilities.podspec; sourceTree = ""; }; DE5CF98C20F686290063FFDD /* GULAppEnvironmentUtilTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GULAppEnvironmentUtilTest.m; sourceTree = ""; }; DE84BBC321D7EC900048A176 /* GULUserDefaultsTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GULUserDefaultsTests.m; sourceTree = ""; }; DEC977D320F68C3300014E20 /* GULReachabilityCheckerTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GULReachabilityCheckerTest.m; sourceTree = ""; }; @@ -671,6 +672,7 @@ DEC9786A20F6D66300014E20 /* GULMutableDictionaryTest.m in Sources */, DE84BBC621D7EC900048A176 /* GULUserDefaultsTests.m in Sources */, DEC9786C20F6D66700014E20 /* GULReachabilityCheckerTest.m in Sources */, + 9A414672225259F900B08D77 /* GULAppDelegateSwizzlerTest.m in Sources */, DEC9786820F6D65B00014E20 /* GULLoggerTest.m in Sources */, DEC9786D20F6D66B00014E20 /* GULAppEnvironmentUtilTest.m in Sources */, ); @@ -1101,6 +1103,7 @@ HEADER_SEARCH_PATHS = ( "$(inherited)", "$(SRCROOT)/../Reachability", + "$(SRCROOT)/../AppDelegateSwizzler/Internal", ); INFOPLIST_FILE = "Tests/Tests-Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; @@ -1131,6 +1134,7 @@ HEADER_SEARCH_PATHS = ( "$(inherited)", "$(SRCROOT)/../Reachability", + "$(SRCROOT)/../AppDelegateSwizzler/Internal", ); INFOPLIST_FILE = "Tests/Tests-Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; diff --git a/GoogleUtilities/Example/Podfile b/GoogleUtilities/Example/Podfile index aa230a65d94..e114b6fa8d2 100644 --- a/GoogleUtilities/Example/Podfile +++ b/GoogleUtilities/Example/Podfile @@ -9,16 +9,6 @@ target 'Example_iOS' do inherit! :search_paths pod 'OCMock' end - - post_install do |installer_representation| - installer_representation.pods_project.targets.each do |target| - target.build_configurations.each do |config| - if config.name != 'Release' - config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= ['$(inherited)', 'GUL_UNSWIZZLING_ENABLED=1', 'GUL_APP_DELEGATE_TESTING=1'] - end - end - end - end end target 'Example_macOS' do @@ -42,3 +32,13 @@ target 'Example_tvOS' do pod 'OCMock' end end + +post_install do |installer_representation| + installer_representation.pods_project.targets.each do |target| + target.build_configurations.each do |config| + if config.name != 'Release' + config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= ['$(inherited)', 'GUL_UNSWIZZLING_ENABLED=1', 'GUL_APP_DELEGATE_TESTING=1'] + end + end + end +end \ No newline at end of file diff --git a/GoogleUtilities/Example/Tests/Swizzler/GULAppDelegateSwizzlerTest.m b/GoogleUtilities/Example/Tests/Swizzler/GULAppDelegateSwizzlerTest.m index 17c990a4ef8..077aaa4d427 100644 --- a/GoogleUtilities/Example/Tests/Swizzler/GULAppDelegateSwizzlerTest.m +++ b/GoogleUtilities/Example/Tests/Swizzler/GULAppDelegateSwizzlerTest.m @@ -155,11 +155,13 @@ - (void)application:(UIApplication *)application self.failToRegisterForRemoteNotificationsError = error; } +#if TARGET_OS_IOS - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo { self.application = application; self.remoteNotification = userInfo; } +#endif // TARGET_OS_IOS - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo @@ -207,6 +209,7 @@ - (BOOL)application:(UIApplication *)app return YES; } +#if TARGET_OS_IOS - (BOOL)application:(UIApplication *)application openURL:(nonnull NSURL *)url sourceApplication:(nullable NSString *)sourceApplication @@ -214,6 +217,7 @@ - (BOOL)application:(UIApplication *)application _URLForIOS8 = [url copy]; return YES; } +#endif // TARGET_OS_IOS #if SDK_HAS_USERACTIVITY @@ -279,8 +283,14 @@ - (void)testProxyAppDelegate { XCTAssertEqual(sizeBefore, sizeAfter); // After being proxied, it should be able to respond to the required method selector. +#if TARGET_OS_IOS XCTAssertTrue([realAppDelegate respondsToSelector:@selector(application:openURL:sourceApplication:annotation:)]); + + XCTAssertTrue([realAppDelegate respondsToSelector:@selector(application: + didReceiveRemoteNotification:)]); +#endif // TARGET_OS_IOS + XCTAssertTrue([realAppDelegate respondsToSelector:@selector(application: continueUserActivity:restorationHandler:)]); XCTAssertTrue([realAppDelegate respondsToSelector:@selector(application:openURL:options:)]); @@ -294,8 +304,7 @@ - (void)testProxyAppDelegate { XCTAssertTrue([realAppDelegate respondsToSelector:@selector(application: didReceiveRemoteNotification:fetchCompletionHandler:)]); - XCTAssertTrue([realAppDelegate respondsToSelector:@selector(application: - didReceiveRemoteNotification:)]); + // Make sure that the class has changed. XCTAssertNotEqualObjects([realAppDelegate class], realAppDelegateClassBefore); @@ -511,6 +520,7 @@ - (void)testResultOfApplicationOpenURLOptionsIsORed { XCTAssertTrue(shouldOpen); } +#if TARGET_OS_IOS /** Tests that application:openURL:sourceApplication:annotation: is invoked on the interceptors if * it exists. */ @@ -593,6 +603,7 @@ - (void)testApplicationOpenURLSourceApplicationAnnotationResultIsORed { // The result is YES if one of the interceptors returns YES. XCTAssertTrue(shouldOpen); } +#endif // TARGET_OS_IOS /** Tests that application:handleEventsForBackgroundURLSession:completionHandler: is invoked on the * interceptors if it exists. @@ -753,6 +764,7 @@ - (void)testApplicationDidFailToRegisterForRemoteNotificationsIsInvokedOnInterce } - (void)testApplicationDidReceiveRemoteNotificationIsInvokedOnInterceptors { +#if TARGET_OS_IOS NSDictionary *notification = @{}; UIApplication *application = [UIApplication sharedApplication]; @@ -773,6 +785,7 @@ - (void)testApplicationDidReceiveRemoteNotificationIsInvokedOnInterceptors { XCTAssertEqual(testAppDelegate.application, application); XCTAssertEqual(testAppDelegate.remoteNotification, notification); +#endif // TARGET_OS_IOS } - (void)testApplicationDidReceiveRemoteNotificationWithCompletionIsInvokedOnInterceptors { From 6dee8d3beb1b674338b7ca72e1a08ba6d4961b7b Mon Sep 17 00:00:00 2001 From: Maksym Malyhin Date: Mon, 1 Apr 2019 11:44:27 -0400 Subject: [PATCH 20/24] style.sh generated changes --- .../GULAppDelegateSwizzler.m | 20 +++++++++---------- .../Swizzler/GULAppDelegateSwizzlerTest.m | 12 +++++------ 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/GoogleUtilities/AppDelegateSwizzler/GULAppDelegateSwizzler.m b/GoogleUtilities/AppDelegateSwizzler/GULAppDelegateSwizzler.m index e0bf2a05552..bbf5d8e1e9a 100644 --- a/GoogleUtilities/AppDelegateSwizzler/GULAppDelegateSwizzler.m +++ b/GoogleUtilities/AppDelegateSwizzler/GULAppDelegateSwizzler.m @@ -71,7 +71,7 @@ typedef void (*GULRealDidReceiveRemoteNotificationWithCompletionIMP)( "GUL_openURLSourceApplicationAnnotationIMP"; static char const *const kGULRealDidReceiveRemoteNotificationIMPKey = "GUL_didReceiveRemoteNotificationIMP"; -#endif // TARGET_OS_IOS +#endif // TARGET_OS_IOS static char const *const kGULRealDidRegisterForRemoteNotificationsIMPKey = "GUL_didRegisterForRemoteNotificationsIMP"; @@ -379,12 +379,12 @@ + (void)createSubclassWithObject:(id)anObject { fromClass:[GULAppDelegateSwizzler class] toClass:appDelegateSubClass]; GULRealDidReceiveRemoteNotificationIMP didReceiveRemoteNotificationIMP = - (GULRealDidReceiveRemoteNotificationIMP) - [GULAppDelegateSwizzler implementationOfMethodSelector:didReceiveRemoteNotificationSEL - fromClass:realClass]; + (GULRealDidReceiveRemoteNotificationIMP) + [GULAppDelegateSwizzler implementationOfMethodSelector:didReceiveRemoteNotificationSEL + fromClass:realClass]; NSValue *didReceiveRemoteNotificationIMPPointer = - [NSValue valueWithPointer:didReceiveRemoteNotificationIMP]; -#endif // TARGET_OS_IOS + [NSValue valueWithPointer:didReceiveRemoteNotificationIMP]; +#endif // TARGET_OS_IOS // For application:handleEventsForBackgroundURLSession:completionHandler: SEL handleEventsForBackgroundURLSessionSEL = @selector(application: @@ -472,8 +472,8 @@ + (void)createSubclassWithObject:(id)anObject { objc_setAssociatedObject(anObject, &kGULRealDidReceiveRemoteNotificationIMPKey, didReceiveRemoteNotificationIMPPointer, OBJC_ASSOCIATION_RETAIN_NONATOMIC); -#endif // TARGET_OS_IOS - +#endif // TARGET_OS_IOS + objc_setAssociatedObject(anObject, &kGULRealDidRegisterForRemoteNotificationsIMPKey, didRegisterForRemoteNotificationsIMPPointer, OBJC_ASSOCIATION_RETAIN_NONATOMIC); @@ -697,7 +697,7 @@ - (BOOL)application:(UIApplication *)application return returnedValue; } -#endif // TARGET_OS_IOS +#endif // TARGET_OS_IOS #pragma mark - [Donor Methods] Network overridden handler methods @@ -852,7 +852,7 @@ - (void)application:(UIApplication *)application } } -#endif // TARGET_OS_IOS +#endif // TARGET_OS_IOS + (void)proxyAppDelegate:(id)appDelegate { if (![appDelegate conformsToProtocol:@protocol(UIApplicationDelegate)]) { diff --git a/GoogleUtilities/Example/Tests/Swizzler/GULAppDelegateSwizzlerTest.m b/GoogleUtilities/Example/Tests/Swizzler/GULAppDelegateSwizzlerTest.m index 077aaa4d427..b0f07fe4348 100644 --- a/GoogleUtilities/Example/Tests/Swizzler/GULAppDelegateSwizzlerTest.m +++ b/GoogleUtilities/Example/Tests/Swizzler/GULAppDelegateSwizzlerTest.m @@ -161,7 +161,7 @@ - (void)application:(UIApplication *)application self.application = application; self.remoteNotification = userInfo; } -#endif // TARGET_OS_IOS +#endif // TARGET_OS_IOS - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo @@ -217,7 +217,7 @@ - (BOOL)application:(UIApplication *)application _URLForIOS8 = [url copy]; return YES; } -#endif // TARGET_OS_IOS +#endif // TARGET_OS_IOS #if SDK_HAS_USERACTIVITY @@ -288,8 +288,8 @@ - (void)testProxyAppDelegate { respondsToSelector:@selector(application:openURL:sourceApplication:annotation:)]); XCTAssertTrue([realAppDelegate respondsToSelector:@selector(application: - didReceiveRemoteNotification:)]); -#endif // TARGET_OS_IOS + didReceiveRemoteNotification:)]); +#endif // TARGET_OS_IOS XCTAssertTrue([realAppDelegate respondsToSelector:@selector(application: continueUserActivity:restorationHandler:)]); @@ -603,7 +603,7 @@ - (void)testApplicationOpenURLSourceApplicationAnnotationResultIsORed { // The result is YES if one of the interceptors returns YES. XCTAssertTrue(shouldOpen); } -#endif // TARGET_OS_IOS +#endif // TARGET_OS_IOS /** Tests that application:handleEventsForBackgroundURLSession:completionHandler: is invoked on the * interceptors if it exists. @@ -785,7 +785,7 @@ - (void)testApplicationDidReceiveRemoteNotificationIsInvokedOnInterceptors { XCTAssertEqual(testAppDelegate.application, application); XCTAssertEqual(testAppDelegate.remoteNotification, notification); -#endif // TARGET_OS_IOS +#endif // TARGET_OS_IOS } - (void)testApplicationDidReceiveRemoteNotificationWithCompletionIsInvokedOnInterceptors { From 2726e7c9a66fe20cc9b20e1b4672110b556e5ba4 Mon Sep 17 00:00:00 2001 From: Maksym Malyhin Date: Mon, 1 Apr 2019 14:07:18 -0400 Subject: [PATCH 21/24] FirebaseMessaging - missing dependencies added --- FirebaseMessaging.podspec | 1 + 1 file changed, 1 insertion(+) diff --git a/FirebaseMessaging.podspec b/FirebaseMessaging.podspec index 535a575c049..e1d36c8e70c 100644 --- a/FirebaseMessaging.podspec +++ b/FirebaseMessaging.podspec @@ -41,6 +41,7 @@ device, and it is completely free. s.dependency 'FirebaseAnalyticsInterop', '~> 1.1' s.dependency 'FirebaseCore', '~> 5.2' s.dependency 'FirebaseInstanceID', '~> 3.6' + s.dependency 'GoogleUtilities/AppDelegateSwizzler', '~> 5.3' s.dependency 'GoogleUtilities/Reachability', '~> 5.3' s.dependency 'GoogleUtilities/Environment', '~> 5.3' s.dependency 'GoogleUtilities/UserDefaults', '~> 5.3' From 63fd1f5f716a0ca36ecef9ab915786c79cc05bf8 Mon Sep 17 00:00:00 2001 From: Maksym Malyhin Date: Mon, 1 Apr 2019 15:18:38 -0400 Subject: [PATCH 22/24] FCM data channel messages - pass data to [FIRMessaging appDidReceiveMessage:] before calling delegate. --- Firebase/Messaging/FIRMessaging.m | 1 + 1 file changed, 1 insertion(+) diff --git a/Firebase/Messaging/FIRMessaging.m b/Firebase/Messaging/FIRMessaging.m index 6a16840eb61..997a2e68201 100644 --- a/Firebase/Messaging/FIRMessaging.m +++ b/Firebase/Messaging/FIRMessaging.m @@ -880,6 +880,7 @@ + (NSString *)FIRMessagingSDKCurrentLocale { - (void)receiver:(FIRMessagingReceiver *)receiver receivedRemoteMessage:(FIRMessagingRemoteMessage *)remoteMessage { if ([self.delegate respondsToSelector:@selector(messaging:didReceiveMessage:)]) { + [self appDidReceiveMessage:remoteMessage.appData]; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunguarded-availability" [self.delegate messaging:self didReceiveMessage:remoteMessage]; From 4a3fbb15b161bf01fb5f585991602800977ad515 Mon Sep 17 00:00:00 2001 From: Maksym Malyhin Date: Tue, 2 Apr 2019 10:37:56 -0400 Subject: [PATCH 23/24] FIRMessagingRemoteNotificationsProxyTest - remove GULLoggerForceDebug() --- .../Messaging/Tests/FIRMessagingRemoteNotificationsProxyTest.m | 2 -- 1 file changed, 2 deletions(-) diff --git a/Example/Messaging/Tests/FIRMessagingRemoteNotificationsProxyTest.m b/Example/Messaging/Tests/FIRMessagingRemoteNotificationsProxyTest.m index 4988c03a297..dae5297fa6a 100644 --- a/Example/Messaging/Tests/FIRMessagingRemoteNotificationsProxyTest.m +++ b/Example/Messaging/Tests/FIRMessagingRemoteNotificationsProxyTest.m @@ -24,7 +24,6 @@ #import "FIRMessaging.h" #import "FIRMessagingRemoteNotificationsProxy.h" -#import #import #pragma mark - Invalid App Delegate or UNNotificationCenter @@ -144,7 +143,6 @@ @implementation FIRMessagingRemoteNotificationsProxyTest - (void)setUp { [super setUp]; - GULLoggerForceDebug(); [GULAppDelegateSwizzler resetProxyOriginalDelegateOnceToken]; _mockSharedApplication = OCMPartialMock([UIApplication sharedApplication]); From b390f4f8ed2e4a39a0259f1af5c36efefd0eb0d6 Mon Sep 17 00:00:00 2001 From: Maksym Malyhin Date: Tue, 2 Apr 2019 11:09:59 -0400 Subject: [PATCH 24/24] GULAppDelegateSwizzler - add deprecated `application:didReceiveRemoteNotification:` for tvOS just in case --- .../GULAppDelegateSwizzler.m | 43 +++++++++---------- .../Swizzler/GULAppDelegateSwizzlerTest.m | 15 ++++--- 2 files changed, 28 insertions(+), 30 deletions(-) diff --git a/GoogleUtilities/AppDelegateSwizzler/GULAppDelegateSwizzler.m b/GoogleUtilities/AppDelegateSwizzler/GULAppDelegateSwizzler.m index bbf5d8e1e9a..b1b1545b11a 100644 --- a/GoogleUtilities/AppDelegateSwizzler/GULAppDelegateSwizzler.m +++ b/GoogleUtilities/AppDelegateSwizzler/GULAppDelegateSwizzler.m @@ -69,15 +69,14 @@ typedef void (*GULRealDidReceiveRemoteNotificationWithCompletionIMP)( #if TARGET_OS_IOS static char const *const kGULOpenURLOptionsSourceAnnotationsIMPKey = "GUL_openURLSourceApplicationAnnotationIMP"; -static char const *const kGULRealDidReceiveRemoteNotificationIMPKey = - "GUL_didReceiveRemoteNotificationIMP"; #endif // TARGET_OS_IOS static char const *const kGULRealDidRegisterForRemoteNotificationsIMPKey = "GUL_didRegisterForRemoteNotificationsIMP"; static char const *const kGULRealDidFailToRegisterForRemoteNotificationsIMPKey = "GUL_didFailToRegisterForRemoteNotificationsIMP"; - +static char const *const kGULRealDidReceiveRemoteNotificationIMPKey = +"GUL_didReceiveRemoteNotificationIMP"; static char const *const kGULRealDidReceiveRemoteNotificationWithCompletionIMPKey = "GUL_didReceiveRemoteNotificationWithCompletionIMP"; @@ -372,18 +371,6 @@ + (void)createSubclassWithObject:(id)anObject { fromClass:realClass]; NSValue *openURLSourceAppAnnotationIMPPointer = [NSValue valueWithPointer:openURLSourceApplicationAnnotationIMP]; - - // For application:didReceiveRemoteNotification: - SEL didReceiveRemoteNotificationSEL = @selector(application:didReceiveRemoteNotification:); - [GULAppDelegateSwizzler addInstanceMethodWithSelector:didReceiveRemoteNotificationSEL - fromClass:[GULAppDelegateSwizzler class] - toClass:appDelegateSubClass]; - GULRealDidReceiveRemoteNotificationIMP didReceiveRemoteNotificationIMP = - (GULRealDidReceiveRemoteNotificationIMP) - [GULAppDelegateSwizzler implementationOfMethodSelector:didReceiveRemoteNotificationSEL - fromClass:realClass]; - NSValue *didReceiveRemoteNotificationIMPPointer = - [NSValue valueWithPointer:didReceiveRemoteNotificationIMP]; #endif // TARGET_OS_IOS // For application:handleEventsForBackgroundURLSession:completionHandler: @@ -425,6 +412,18 @@ + (void)createSubclassWithObject:(id)anObject { NSValue *didFailToRegisterForRemoteNotificationsIMPPointer = [NSValue valueWithPointer:didFailToRegisterForRemoteNotificationsIMP]; + // For application:didReceiveRemoteNotification: + SEL didReceiveRemoteNotificationSEL = @selector(application:didReceiveRemoteNotification:); + [GULAppDelegateSwizzler addInstanceMethodWithSelector:didReceiveRemoteNotificationSEL + fromClass:[GULAppDelegateSwizzler class] + toClass:appDelegateSubClass]; + GULRealDidReceiveRemoteNotificationIMP didReceiveRemoteNotificationIMP = + (GULRealDidReceiveRemoteNotificationIMP) + [GULAppDelegateSwizzler implementationOfMethodSelector:didReceiveRemoteNotificationSEL + fromClass:realClass]; + NSValue *didReceiveRemoteNotificationIMPPointer = + [NSValue valueWithPointer:didReceiveRemoteNotificationIMP]; + // For application:didReceiveRemoteNotification:fetchCompletionHandler: NSValue *didReceiveRemoteNotificationWithCompletionIMPPointer; SEL didReceiveRemoteNotificationWithCompletionSEL = @@ -468,10 +467,6 @@ + (void)createSubclassWithObject:(id)anObject { #if TARGET_OS_IOS objc_setAssociatedObject(anObject, &kGULOpenURLOptionsSourceAnnotationsIMPKey, openURLSourceAppAnnotationIMPPointer, OBJC_ASSOCIATION_RETAIN_NONATOMIC); - - objc_setAssociatedObject(anObject, &kGULRealDidReceiveRemoteNotificationIMPKey, - didReceiveRemoteNotificationIMPPointer, - OBJC_ASSOCIATION_RETAIN_NONATOMIC); #endif // TARGET_OS_IOS objc_setAssociatedObject(anObject, &kGULRealDidRegisterForRemoteNotificationsIMPKey, @@ -480,6 +475,9 @@ + (void)createSubclassWithObject:(id)anObject { objc_setAssociatedObject(anObject, &kGULRealDidFailToRegisterForRemoteNotificationsIMPKey, didFailToRegisterForRemoteNotificationsIMPPointer, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + objc_setAssociatedObject(anObject, &kGULRealDidReceiveRemoteNotificationIMPKey, + didReceiveRemoteNotificationIMPPointer, + OBJC_ASSOCIATION_RETAIN_NONATOMIC); objc_setAssociatedObject(anObject, &kGULRealDidReceiveRemoteNotificationWithCompletionIMPKey, didReceiveRemoteNotificationWithCompletionIMPPointer, OBJC_ASSOCIATION_RETAIN_NONATOMIC); @@ -829,8 +827,6 @@ - (void)application:(UIApplication *)application } } -#if TARGET_OS_IOS - - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo { NSValue *didReceiveRemoteNotificationIMPPointer = @@ -840,20 +836,21 @@ - (void)application:(UIApplication *)application // Notify interceptors. SEL methodSelector = @selector(application:didReceiveRemoteNotification:); +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" [GULAppDelegateSwizzler notifyInterceptorsWithMethodSelector:methodSelector callback:^(id interceptor) { [interceptor application:application didReceiveRemoteNotification:userInfo]; }]; +#pragma clang diagnostic pop // Call the real implementation if the real App Delegate has any. if (didReceiveRemoteNotificationIMP) { didReceiveRemoteNotificationIMP(self, methodSelector, application, userInfo); } } -#endif // TARGET_OS_IOS - + (void)proxyAppDelegate:(id)appDelegate { if (![appDelegate conformsToProtocol:@protocol(UIApplicationDelegate)]) { GULLogNotice( diff --git a/GoogleUtilities/Example/Tests/Swizzler/GULAppDelegateSwizzlerTest.m b/GoogleUtilities/Example/Tests/Swizzler/GULAppDelegateSwizzlerTest.m index b0f07fe4348..1f600b6c538 100644 --- a/GoogleUtilities/Example/Tests/Swizzler/GULAppDelegateSwizzlerTest.m +++ b/GoogleUtilities/Example/Tests/Swizzler/GULAppDelegateSwizzlerTest.m @@ -155,13 +155,14 @@ - (void)application:(UIApplication *)application self.failToRegisterForRemoteNotificationsError = error; } -#if TARGET_OS_IOS +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-implementations" - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo { self.application = application; self.remoteNotification = userInfo; } -#endif // TARGET_OS_IOS +#pragma clang diagnostic pop - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo @@ -286,9 +287,6 @@ - (void)testProxyAppDelegate { #if TARGET_OS_IOS XCTAssertTrue([realAppDelegate respondsToSelector:@selector(application:openURL:sourceApplication:annotation:)]); - - XCTAssertTrue([realAppDelegate respondsToSelector:@selector(application: - didReceiveRemoteNotification:)]); #endif // TARGET_OS_IOS XCTAssertTrue([realAppDelegate respondsToSelector:@selector(application: @@ -301,6 +299,8 @@ - (void)testProxyAppDelegate { respondsToSelector:@selector(application:didRegisterForRemoteNotificationsWithDeviceToken:)]); XCTAssertTrue([realAppDelegate respondsToSelector:@selector(application:didFailToRegisterForRemoteNotificationsWithError:)]); + XCTAssertTrue([realAppDelegate respondsToSelector:@selector(application: + didReceiveRemoteNotification:)]); XCTAssertTrue([realAppDelegate respondsToSelector:@selector(application: didReceiveRemoteNotification:fetchCompletionHandler:)]); @@ -763,8 +763,9 @@ - (void)testApplicationDidFailToRegisterForRemoteNotificationsIsInvokedOnInterce XCTAssertEqual(testAppDelegate.failToRegisterForRemoteNotificationsError, error); } +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" - (void)testApplicationDidReceiveRemoteNotificationIsInvokedOnInterceptors { -#if TARGET_OS_IOS NSDictionary *notification = @{}; UIApplication *application = [UIApplication sharedApplication]; @@ -785,8 +786,8 @@ - (void)testApplicationDidReceiveRemoteNotificationIsInvokedOnInterceptors { XCTAssertEqual(testAppDelegate.application, application); XCTAssertEqual(testAppDelegate.remoteNotification, notification); -#endif // TARGET_OS_IOS } +#pragma clang diagnostic pop - (void)testApplicationDidReceiveRemoteNotificationWithCompletionIsInvokedOnInterceptors { NSDictionary *notification = @{};