Skip to content

Commit d6c1170

Browse files
authored
Handle UISceneDelegate changes in Auth (#4380)
* Handle UISceneDelegate changes in Auth * Fix pr issue * Update GULAppDelegateSwizzler.m * Fix pr issue * Update GULLoggerCodes.h * Update GULLoggerCodes.h * Update GULAppDelegateSwizzler_Private.h * Update GULAppDelegateSwizzlerTest.m
1 parent 0882dbc commit d6c1170

File tree

5 files changed

+292
-16
lines changed

5 files changed

+292
-16
lines changed

Firebase/Auth/Source/Auth/FIRAuth.m

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,11 @@ + (FIRActionCodeOperation)actionCodeOperationForRequestType:(NSString *)requestT
231231
#pragma mark - FIRAuth
232232

233233
#if TARGET_OS_IOS
234+
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000
235+
@interface FIRAuth () <UIApplicationDelegate, UISceneDelegate, FIRLibrary, FIRComponentLifecycleMaintainer>
236+
#else
234237
@interface FIRAuth () <UIApplicationDelegate, FIRLibrary, FIRComponentLifecycleMaintainer>
238+
#endif
235239
#else
236240
@interface FIRAuth () <FIRLibrary, FIRComponentLifecycleMaintainer>
237241
#endif
@@ -1387,7 +1391,17 @@ - (BOOL)canHandleURL:(NSURL *)URL {
13871391
});
13881392
return result;
13891393
}
1390-
#endif
1394+
1395+
#pragma mark - UISceneDelegate
1396+
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000
1397+
- (void)scene:(UIScene *)scene openURLContexts:(NSSet<UIOpenURLContext *> *)URLContexts API_AVAILABLE(ios(13.0)) {
1398+
for (UIOpenURLContext *urlContext in URLContexts) {
1399+
NSURL *url = [urlContext URL];
1400+
[self canHandleURL:url];
1401+
}
1402+
}
1403+
#endif // __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000
1404+
#endif // TARGET_OS_IOS
13911405

13921406
#pragma mark - Internal Methods
13931407

GoogleUtilities/AppDelegateSwizzler/GULAppDelegateSwizzler.m

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ typedef BOOL (*GULRealOpenURLSourceApplicationAnnotationIMP)(
3131
typedef BOOL (*GULRealOpenURLOptionsIMP)(
3232
id, SEL, GULApplication *, NSURL *, NSDictionary<NSString *, id> *);
3333

34+
#if UISCENE_SUPPORTED
35+
API_AVAILABLE(ios(13.0), tvos(13.0))
36+
typedef void (*GULOpenURLContextsIMP)(id, SEL, UIScene *, NSSet<UIOpenURLContext *> *);
37+
#endif // UISCENE_SUPPORTED
38+
3439
#pragma clang diagnostic push
3540
#pragma clang diagnostic ignored "-Wstrict-prototypes"
3641
typedef void (*GULRealHandleEventsForBackgroundURLSessionIMP)(
@@ -289,6 +294,20 @@ + (void)proxyOriginalDelegate {
289294
id<GULApplicationDelegate> originalDelegate =
290295
[GULAppDelegateSwizzler sharedApplication].delegate;
291296
[GULAppDelegateSwizzler proxyAppDelegate:originalDelegate];
297+
298+
#if UISCENE_SUPPORTED
299+
if (@available(iOS 13.0, tvOS 13.0, *)) {
300+
if (![GULAppDelegateSwizzler isAppDelegateProxyEnabled]) {
301+
return;
302+
} else {
303+
[[NSNotificationCenter defaultCenter]
304+
addObserver:self
305+
selector:@selector(handleSceneWillConnectToNotification:)
306+
name:UISceneWillConnectNotification
307+
object:nil];
308+
}
309+
}
310+
#endif // UISCENE_SUPPORTED
292311
});
293312
}
294313

@@ -682,6 +701,17 @@ + (void)notifyInterceptorsWithMethodSelector:(SEL)methodSelector
682701
}];
683702
}
684703

704+
#if UISCENE_SUPPORTED
705+
+ (void)handleSceneWillConnectToNotification:(NSNotification *)notification {
706+
if (@available(iOS 13.0, tvOS 13.0, *)) {
707+
if ([notification.object isKindOfClass:[UIScene class]]) {
708+
UIScene *scene = (UIScene *)notification.object;
709+
[GULAppDelegateSwizzler proxySceneDelegateIfNeeded:scene];
710+
}
711+
}
712+
}
713+
#endif // UISCENE_SUPPORTED
714+
685715
// The methods below are donor methods which are added to the dynamic subclass of the App Delegate.
686716
// They are called within the scope of the real App Delegate so |self| does not refer to the
687717
// GULAppDelegateSwizzler instance but the real App Delegate instance.
@@ -761,6 +791,36 @@ - (BOOL)application:(GULApplication *)application
761791

762792
#endif // TARGET_OS_IOS
763793

794+
#pragma mark - [Donor Methods] UISceneDelegate URL handler
795+
796+
#if UISCENE_SUPPORTED
797+
- (void)scene:(UIScene *)scene
798+
openURLContexts:(NSSet<UIOpenURLContext *> *)URLContexts API_AVAILABLE(ios(13.0), tvos(13.0)) {
799+
if (@available(iOS 13.0, tvOS 13.0, *)) {
800+
SEL methodSelector = @selector(scene:openURLContexts:);
801+
// Call the real implementation if the real App Delegate has any.
802+
NSValue *openURLContextsIMPPointer =
803+
[GULAppDelegateSwizzler originalImplementationForSelector:methodSelector object:self];
804+
GULOpenURLContextsIMP openURLContextsIMP = [openURLContextsIMPPointer pointerValue];
805+
806+
[GULAppDelegateSwizzler
807+
notifyInterceptorsWithMethodSelector:methodSelector
808+
callback:^(id<GULApplicationDelegate> interceptor) {
809+
if ([interceptor
810+
conformsToProtocol:@protocol(UISceneDelegate)]) {
811+
id<UISceneDelegate> sceneInterceptor =
812+
(id<UISceneDelegate>)interceptor;
813+
[sceneInterceptor scene:scene openURLContexts:URLContexts];
814+
}
815+
}];
816+
817+
if (openURLContextsIMP) {
818+
openURLContextsIMP(self, methodSelector, scene, URLContexts);
819+
}
820+
}
821+
}
822+
#endif // UISCENE_SUPPORTED
823+
764824
#pragma mark - [Donor Methods] Network overridden handler methods
765825

766826
#if TARGET_OS_IOS || TARGET_OS_TV
@@ -1002,6 +1062,97 @@ + (void)proxyAppDelegate:(id<GULApplicationDelegate>)appDelegate {
10021062
}
10031063
}
10041064

1065+
#if UISCENE_SUPPORTED
1066+
+ (void)proxySceneDelegateIfNeeded:(UIScene *)scene {
1067+
Class realClass = [scene.delegate class];
1068+
NSString *className = NSStringFromClass(realClass);
1069+
1070+
// Skip proxying if the class has a prefix of kGULAppDelegatePrefix, which means it has been
1071+
// proxied before.
1072+
if ([className hasPrefix:kGULAppDelegatePrefix]) {
1073+
return;
1074+
}
1075+
1076+
NSString *classNameWithPrefix = [kGULAppDelegatePrefix stringByAppendingString:className];
1077+
NSString *newClassName =
1078+
[NSString stringWithFormat:@"%@-%@", classNameWithPrefix, [NSUUID UUID].UUIDString];
1079+
1080+
if (NSClassFromString(newClassName)) {
1081+
GULLogError(
1082+
kGULLoggerSwizzler, NO,
1083+
[NSString
1084+
stringWithFormat:@"I-SWZ%06ld",
1085+
(long)kGULSwizzlerMessageCodeAppDelegateSwizzlingInvalidSceneDelegate],
1086+
@"Cannot create a proxy for Scene Delegate. Subclass already exists. Original Class"
1087+
@": %@, subclass: %@",
1088+
className, newClassName);
1089+
return;
1090+
}
1091+
1092+
// Register the new class as subclass of the real one. Do not allocate more than the real class
1093+
// size.
1094+
Class sceneDelegateSubClass = objc_allocateClassPair(realClass, newClassName.UTF8String, 0);
1095+
if (sceneDelegateSubClass == Nil) {
1096+
GULLogError(
1097+
kGULLoggerSwizzler, NO,
1098+
[NSString
1099+
stringWithFormat:@"I-SWZ%06ld",
1100+
(long)kGULSwizzlerMessageCodeAppDelegateSwizzlingInvalidSceneDelegate],
1101+
@"Cannot create a proxy for Scene Delegate. Subclass already exists. Original Class"
1102+
@": %@, subclass: Nil",
1103+
className);
1104+
return;
1105+
}
1106+
1107+
NSMutableDictionary<NSString *, NSValue *> *realImplementationsBySelector =
1108+
[[NSMutableDictionary alloc] init];
1109+
1110+
// For scene:openURLContexts:
1111+
SEL openURLContextsSEL = @selector(scene:openURLContexts:);
1112+
[self proxyDestinationSelector:openURLContextsSEL
1113+
implementationsFromSourceSelector:openURLContextsSEL
1114+
fromClass:[GULAppDelegateSwizzler class]
1115+
toClass:sceneDelegateSubClass
1116+
realClass:realClass
1117+
storeDestinationImplementationTo:realImplementationsBySelector];
1118+
1119+
// Store original implementations to a fake property of the original delegate.
1120+
objc_setAssociatedObject(scene.delegate, &kGULRealIMPBySelectorKey,
1121+
[realImplementationsBySelector copy], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
1122+
objc_setAssociatedObject(scene.delegate, &kGULRealClassKey, realClass,
1123+
OBJC_ASSOCIATION_RETAIN_NONATOMIC);
1124+
1125+
// The subclass size has to be exactly the same size with the original class size. The subclass
1126+
// cannot have more ivars/properties than its superclass since it will cause an offset in memory
1127+
// that can lead to overwriting the isa of an object in the next frame.
1128+
if (class_getInstanceSize(realClass) != class_getInstanceSize(sceneDelegateSubClass)) {
1129+
GULLogError(
1130+
kGULLoggerSwizzler, NO,
1131+
[NSString
1132+
stringWithFormat:@"I-SWZ%06ld",
1133+
(long)kGULSwizzlerMessageCodeAppDelegateSwizzlingInvalidSceneDelegate],
1134+
@"Cannot create subclass of Scene Delegate, because the created subclass is not the "
1135+
@"same size. %@",
1136+
className);
1137+
NSAssert(NO, @"Classes must be the same size to swizzle isa");
1138+
return;
1139+
}
1140+
1141+
// Make the newly created class to be the subclass of the real Scene Delegate class.
1142+
objc_registerClassPair(sceneDelegateSubClass);
1143+
if (object_setClass(scene.delegate, sceneDelegateSubClass)) {
1144+
GULLogDebug(
1145+
kGULLoggerSwizzler, NO,
1146+
[NSString
1147+
stringWithFormat:@"I-SWZ%06ld",
1148+
(long)kGULSwizzlerMessageCodeAppDelegateSwizzlingInvalidSceneDelegate],
1149+
@"Successfully created Scene Delegate Proxy automatically. To disable the "
1150+
@"proxy, set the flag %@ to NO (Boolean) in the Info.plist",
1151+
[GULAppDelegateSwizzler correctAppDelegateProxyKey]);
1152+
}
1153+
}
1154+
#endif // UISCENE_SUPPORTED
1155+
10051156
#pragma mark - Methods to print correct debug logs
10061157

10071158
+ (NSString *)correctAppDelegateProxyKey {

GoogleUtilities/AppDelegateSwizzler/Internal/GULAppDelegateSwizzler_Private.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@
1818
#import <GoogleUtilities/GULAppDelegateSwizzler.h>
1919
#import <GoogleUtilities/GULMutableDictionary.h>
2020

21+
#if ((TARGET_OS_IOS || TARGET_OS_TV) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= 130000))
22+
#define UISCENE_SUPPORTED 1
23+
#endif
24+
2125
@class GULApplication;
2226

2327
NS_ASSUME_NONNULL_BEGIN
@@ -50,6 +54,17 @@ NS_ASSUME_NONNULL_BEGIN
5054
*/
5155
+ (id<GULApplicationDelegate>)originalDelegate;
5256

57+
#if UISCENE_SUPPORTED
58+
59+
/** ISA Swizzles the given appDelegate as the original app delegate would be.
60+
*
61+
* @param scene The scene whose delegate needs to be isa swizzled. This should conform to the
62+
* scene delegate protocol.
63+
*/
64+
+ (void)proxySceneDelegateIfNeeded:(UIScene *)scene API_AVAILABLE(ios(13.0), tvos(13.0));
65+
66+
#endif // UISCENE_SUPPORTED
67+
5368
@end
5469

5570
NS_ASSUME_NONNULL_END

GoogleUtilities/Common/GULLoggerCodes.h

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,21 +18,22 @@
1818

1919
typedef NS_ENUM(NSInteger, GULSwizzlerMessageCode) {
2020
// App Delegate Swizzling.
21-
kGULSwizzlerMessageCodeAppDelegateSwizzling000 = 1000, // I-SWZ001000
22-
kGULSwizzlerMessageCodeAppDelegateSwizzling001 = 1001, // I-SWZ001001
23-
kGULSwizzlerMessageCodeAppDelegateSwizzling002 = 1002, // I-SWZ001002
24-
kGULSwizzlerMessageCodeAppDelegateSwizzling003 = 1003, // I-SWZ001003
25-
kGULSwizzlerMessageCodeAppDelegateSwizzling004 = 1004, // I-SWZ001004
26-
kGULSwizzlerMessageCodeAppDelegateSwizzling005 = 1005, // I-SWZ001005
27-
kGULSwizzlerMessageCodeAppDelegateSwizzling006 = 1006, // I-SWZ001006
28-
kGULSwizzlerMessageCodeAppDelegateSwizzling007 = 1007, // I-SWZ001007
29-
kGULSwizzlerMessageCodeAppDelegateSwizzling008 = 1008, // I-SWZ001008
30-
kGULSwizzlerMessageCodeAppDelegateSwizzling009 = 1009, // I-SWZ001009
31-
kGULSwizzlerMessageCodeAppDelegateSwizzling010 = 1010, // I-SWZ001010
32-
kGULSwizzlerMessageCodeAppDelegateSwizzling011 = 1011, // I-SWZ001011
33-
kGULSwizzlerMessageCodeAppDelegateSwizzling012 = 1012, // I-SWZ001012
34-
kGULSwizzlerMessageCodeAppDelegateSwizzling013 = 1013, // I-SWZ001013
35-
kGULSwizzlerMessageCodeAppDelegateSwizzlingInvalidAppDelegate = 1014, // I-SWZ001014
21+
kGULSwizzlerMessageCodeAppDelegateSwizzling000 = 1000, // I-SWZ001000
22+
kGULSwizzlerMessageCodeAppDelegateSwizzling001 = 1001, // I-SWZ001001
23+
kGULSwizzlerMessageCodeAppDelegateSwizzling002 = 1002, // I-SWZ001002
24+
kGULSwizzlerMessageCodeAppDelegateSwizzling003 = 1003, // I-SWZ001003
25+
kGULSwizzlerMessageCodeAppDelegateSwizzling004 = 1004, // I-SWZ001004
26+
kGULSwizzlerMessageCodeAppDelegateSwizzling005 = 1005, // I-SWZ001005
27+
kGULSwizzlerMessageCodeAppDelegateSwizzling006 = 1006, // I-SWZ001006
28+
kGULSwizzlerMessageCodeAppDelegateSwizzling007 = 1007, // I-SWZ001007
29+
kGULSwizzlerMessageCodeAppDelegateSwizzling008 = 1008, // I-SWZ001008
30+
kGULSwizzlerMessageCodeAppDelegateSwizzling009 = 1009, // I-SWZ001009
31+
kGULSwizzlerMessageCodeAppDelegateSwizzling010 = 1010, // I-SWZ001010
32+
kGULSwizzlerMessageCodeAppDelegateSwizzling011 = 1011, // I-SWZ001011
33+
kGULSwizzlerMessageCodeAppDelegateSwizzling012 = 1012, // I-SWZ001012
34+
kGULSwizzlerMessageCodeAppDelegateSwizzling013 = 1013, // I-SWZ001013
35+
kGULSwizzlerMessageCodeAppDelegateSwizzlingInvalidAppDelegate = 1014, // I-SWZ001014
36+
kGULSwizzlerMessageCodeAppDelegateSwizzlingInvalidSceneDelegate = 1015, // I-SWZ001015
3637

3738
// Method Swizzling.
3839
kGULSwizzlerMessageCodeMethodSwizzling000 = 2000, // I-SWZ002000

GoogleUtilities/Example/Tests/Swizzler/GULAppDelegateSwizzlerTest.m

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,20 @@ - (BOOL)application:(GULApplication *)application
280280

281281
@end
282282

283+
#pragma mark - Scene Delegate
284+
285+
#if ((TARGET_OS_IOS || TARGET_OS_TV) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= 130000))
286+
@protocol TestSceneProtocol <GULApplicationDelegate, UISceneDelegate>
287+
@end
288+
289+
API_AVAILABLE(ios(13.0), tvos(13.0))
290+
@interface GULTestSceneDelegate : NSObject <UISceneDelegate>
291+
@end
292+
293+
@implementation GULTestSceneDelegate
294+
@end
295+
#endif
296+
283297
@interface GULAppDelegateSwizzlerTest : XCTestCase
284298
@property(nonatomic, strong) id mockSharedApplication;
285299
@end
@@ -1301,4 +1315,85 @@ - (void)testAppDelegateIsProxiedIncludingAPNSMethodsWhenEnabled {
13011315
XCTAssertNotEqualObjects([originalAppDelegate class], originalAppDelegateClass);
13021316
}
13031317

1318+
#pragma mark - Test UISceneDelegate proxy
1319+
1320+
#if ((TARGET_OS_IOS || TARGET_OS_TV) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= 130000))
1321+
1322+
- (void)testProxySceneDelegate {
1323+
if (@available(iOS 13, tvOS 13, *)) {
1324+
GULTestSceneDelegate *realSceneDelegate = [[GULTestSceneDelegate alloc] init];
1325+
id mockSharedScene = OCMClassMock([UIScene class]);
1326+
OCMStub([mockSharedScene delegate]).andReturn(realSceneDelegate);
1327+
size_t sizeBefore = class_getInstanceSize([GULTestSceneDelegate class]);
1328+
1329+
Class realSceneDelegateClassBefore = [realSceneDelegate class];
1330+
1331+
[GULAppDelegateSwizzler proxySceneDelegateIfNeeded:mockSharedScene];
1332+
1333+
XCTAssertTrue([realSceneDelegate isKindOfClass:[GULTestSceneDelegate class]]);
1334+
1335+
NSString *newClassName = NSStringFromClass([realSceneDelegate class]);
1336+
XCTAssertTrue([newClassName hasPrefix:@"GUL_"]);
1337+
// It is no longer GULTestSceneDelegate class instance.
1338+
XCTAssertFalse([realSceneDelegate isMemberOfClass:[GULTestSceneDelegate class]]);
1339+
1340+
size_t sizeAfter = class_getInstanceSize([realSceneDelegate class]);
1341+
1342+
// Class size must stay the same.
1343+
XCTAssertEqual(sizeBefore, sizeAfter);
1344+
1345+
// After being proxied, it should be able to respond to the required method selector.
1346+
XCTAssertTrue([realSceneDelegate respondsToSelector:@selector(scene:openURLContexts:)]);
1347+
1348+
// Make sure that the class has changed.
1349+
XCTAssertNotEqualObjects([realSceneDelegate class], realSceneDelegateClassBefore);
1350+
}
1351+
}
1352+
1353+
- (void)testProxyProxiedSceneDelegate {
1354+
if (@available(iOS 13, tvOS 13, *)) {
1355+
GULTestSceneDelegate *realSceneDelegate = [[GULTestSceneDelegate alloc] init];
1356+
id mockSharedScene = OCMClassMock([UIScene class]);
1357+
OCMStub([mockSharedScene delegate]).andReturn(realSceneDelegate);
1358+
1359+
// Proxy the scene delegate for the 1st time.
1360+
[GULAppDelegateSwizzler proxySceneDelegateIfNeeded:mockSharedScene];
1361+
1362+
Class realSceneDelegateClassBefore = [realSceneDelegate class];
1363+
1364+
// Proxy the scene delegate for the 2nd time.
1365+
[GULAppDelegateSwizzler proxySceneDelegateIfNeeded:mockSharedScene];
1366+
1367+
// Make sure that the class isn't changed.
1368+
XCTAssertEqualObjects([realSceneDelegate class], realSceneDelegateClassBefore);
1369+
}
1370+
}
1371+
1372+
- (void)testSceneOpenURLContextsIsInvokedOnInterceptors {
1373+
if (@available(iOS 13, tvOS 13, *)) {
1374+
NSSet *urlContexts = [NSSet set];
1375+
1376+
GULTestSceneDelegate *realSceneDelegate = [[GULTestSceneDelegate alloc] init];
1377+
id mockSharedScene = OCMClassMock([UIScene class]);
1378+
OCMStub([mockSharedScene delegate]).andReturn(realSceneDelegate);
1379+
1380+
id interceptor = OCMProtocolMock(@protocol(TestSceneProtocol));
1381+
OCMExpect([interceptor scene:mockSharedScene openURLContexts:urlContexts]);
1382+
1383+
id interceptor2 = OCMProtocolMock(@protocol(TestSceneProtocol));
1384+
OCMExpect([interceptor2 scene:mockSharedScene openURLContexts:urlContexts]);
1385+
1386+
[GULAppDelegateSwizzler proxySceneDelegateIfNeeded:mockSharedScene];
1387+
1388+
[GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor];
1389+
[GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor2];
1390+
1391+
[realSceneDelegate scene:mockSharedScene openURLContexts:urlContexts];
1392+
OCMVerifyAll(interceptor);
1393+
OCMVerifyAll(interceptor2);
1394+
}
1395+
}
1396+
1397+
#endif
1398+
13041399
@end

0 commit comments

Comments
 (0)