1 // Copyright 2018 Google LLC
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
7 // http://www.apache.org/licenses/LICENSE-2.0
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
15 #import "TargetConditionals.h"
19 #import <GoogleUtilities/GULAppEnvironmentUtil.h>
20 #import <GoogleUtilities/GULLogger.h>
21 #import <GoogleUtilities/GULMutableDictionary.h>
22 #import "../Common/GULLoggerCodes.h"
23 #import "Internal/GULAppDelegateSwizzler_Private.h"
24 #import "Private/GULAppDelegateSwizzler.h"
26 #import <UIKit/UIKit.h>
27 #import <objc/runtime.h>
29 // Implementations need to be typed before calling the implementation directly to cast the
30 // arguments and the return types correctly. Otherwise, it will crash the app.
31 typedef BOOL (*GULRealOpenURLSourceApplicationAnnotationIMP)(
32 id, SEL, UIApplication *, NSURL *, NSString *, id);
34 typedef BOOL (*GULRealOpenURLOptionsIMP)(
35 id, SEL, UIApplication *, NSURL *, NSDictionary<NSString *, id> *);
37 #pragma clang diagnostic push
38 #pragma clang diagnostic ignored "-Wstrict-prototypes"
39 typedef void (*GULRealHandleEventsForBackgroundURLSessionIMP)(
40 id, SEL, UIApplication *, NSString *, void (^)());
41 #pragma clang diagnostic pop
43 // This is needed to for the library to be warning free on iOS versions < 8.
44 #pragma clang diagnostic push
45 #pragma clang diagnostic ignored "-Wunguarded-availability"
46 typedef BOOL (*GULRealContinueUserActivityIMP)(
47 id, SEL, UIApplication *, NSUserActivity *, void (^)(NSArray *restorableObjects));
48 #pragma clang diagnostic pop
50 typedef void (^GULAppDelegateInterceptorCallback)(id<UIApplicationDelegate>);
52 // The strings below are the keys for associated objects.
53 static char const *const kGULContinueUserActivityIMPKey = "GUL_continueUserActivityIMP";
54 static char const *const kGULHandleBackgroundSessionIMPKey = "GUL_handleBackgroundSessionIMP";
55 static char const *const kGULOpenURLOptionsIMPKey = "GUL_openURLOptionsIMP";
56 static char const *const kGULOpenURLOptionsSourceAnnotationsIMPKey =
57 "GUL_openURLSourceApplicationAnnotationIMP";
58 static char const *const kGULRealClassKey = "GUL_realClass";
59 static NSString *const kGULAppDelegateKeyPath = @"delegate";
61 static GULLoggerService kGULLoggerSwizzler = @"[GoogleUtilities/AppDelegateSwizzler]";
63 // Since Firebase SDKs also use this for app delegate proxying, in order to not be a breaking change
64 // we disable App Delegate proxying when either of these two flags are set to NO.
66 /** Plist key that allows Firebase developers to disable App Delegate Proxying. */
67 static NSString *const kGULFirebaseAppDelegateProxyEnabledPlistKey =
68 @"FirebaseAppDelegateProxyEnabled";
70 /** Plist key that allows developers not using Firebase to disable App Delegate Proxying. */
71 static NSString *const kGULGoogleUtilitiesAppDelegateProxyEnabledPlistKey =
72 @"GoogleUtilitiesAppDelegateProxyEnabled";
74 /** The prefix of the App Delegate. */
75 static NSString *const kGULAppDelegatePrefix = @"GUL_";
77 /** The original instance of App Delegate. */
78 static id<UIApplicationDelegate> gOriginalAppDelegate;
81 * This class is necessary to store the delegates in an NSArray without retaining them.
82 * [NSValue valueWithNonRetainedObject] also provides this functionality, but does not provide a
83 * zeroing pointer. This will cause EXC_BAD_ACCESS when trying to access the object after it is
84 * dealloced. Instead, this container stores a weak, zeroing reference to the object, which
85 * automatically is set to nil by the runtime when the object is dealloced.
87 @interface GULZeroingWeakContainer : NSObject
89 /** Stores a weak object. */
90 @property(nonatomic, weak) id object;
94 @implementation GULZeroingWeakContainer
97 @interface GULAppDelegateObserver : NSObject
100 @implementation GULAppDelegateObserver {
104 + (GULAppDelegateObserver *)sharedInstance {
105 static GULAppDelegateObserver *instance;
106 static dispatch_once_t once;
107 dispatch_once(&once, ^{
108 instance = [[GULAppDelegateObserver alloc] init];
113 - (void)observeUIApplication {
117 [[GULAppDelegateSwizzler sharedApplication]
119 forKeyPath:kGULAppDelegateKeyPath
120 options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
125 - (void)observeValueForKeyPath:(NSString *)keyPath
127 change:(NSDictionary *)change
128 context:(void *)context {
129 if ([keyPath isEqual:kGULAppDelegateKeyPath]) {
130 id newValue = change[NSKeyValueChangeNewKey];
131 id oldValue = change[NSKeyValueChangeOldKey];
132 if ([newValue isEqual:oldValue]) {
135 // Free the stored app delegate instance because it has been changed to a different instance to
136 // avoid keeping it alive forever.
137 if ([oldValue isEqual:gOriginalAppDelegate]) {
138 gOriginalAppDelegate = nil;
139 // Remove the observer. Parse it to NSObject to avoid warning.
140 [[GULAppDelegateSwizzler sharedApplication] removeObserver:self
141 forKeyPath:kGULAppDelegateKeyPath];
149 @implementation GULAppDelegateSwizzler
151 static dispatch_once_t sProxyAppDelegateOnceToken;
153 #pragma mark - Public methods
155 + (BOOL)isAppDelegateProxyEnabled {
156 NSDictionary *infoDictionary = [NSBundle mainBundle].infoDictionary;
158 id isFirebaseProxyEnabledPlistValue = infoDictionary[kGULFirebaseAppDelegateProxyEnabledPlistKey];
159 id isGoogleProxyEnabledPlistValue =
160 infoDictionary[kGULGoogleUtilitiesAppDelegateProxyEnabledPlistKey];
162 // Enabled by default.
163 BOOL isFirebaseAppDelegateProxyEnabled = YES;
164 BOOL isGoogleUtilitiesAppDelegateProxyEnabled = YES;
166 if ([isFirebaseProxyEnabledPlistValue isKindOfClass:[NSNumber class]]) {
167 isFirebaseAppDelegateProxyEnabled = [isFirebaseProxyEnabledPlistValue boolValue];
170 if ([isGoogleProxyEnabledPlistValue isKindOfClass:[NSNumber class]]) {
171 isGoogleUtilitiesAppDelegateProxyEnabled = [isGoogleProxyEnabledPlistValue boolValue];
174 // Only deactivate the proxy if it is explicitly disabled by app developers using either one of
176 return isFirebaseAppDelegateProxyEnabled && isGoogleUtilitiesAppDelegateProxyEnabled;
179 + (GULAppDelegateInterceptorID)registerAppDelegateInterceptor:
180 (id<UIApplicationDelegate>)interceptor {
181 NSAssert(interceptor, @"AppDelegateProxy cannot add nil interceptor");
182 NSAssert([interceptor conformsToProtocol:@protocol(UIApplicationDelegate)],
183 @"AppDelegateProxy interceptor does not conform to UIApplicationDelegate");
186 GULLogError(kGULLoggerSwizzler, NO,
187 [NSString stringWithFormat:@"I-SWZ%06ld",
188 (long)kGULSwizzlerMessageCodeAppDelegateSwizzling000],
189 @"AppDelegateProxy cannot add nil interceptor.");
192 if (![interceptor conformsToProtocol:@protocol(UIApplicationDelegate)]) {
193 GULLogError(kGULLoggerSwizzler, NO,
194 [NSString stringWithFormat:@"I-SWZ%06ld",
195 (long)kGULSwizzlerMessageCodeAppDelegateSwizzling001],
196 @"AppDelegateProxy interceptor does not conform to UIApplicationDelegate");
200 // The ID should be the same given the same interceptor object.
201 NSString *interceptorID = [NSString stringWithFormat:@"%@%p", kGULAppDelegatePrefix, interceptor];
202 if (!interceptorID.length) {
203 GULLogError(kGULLoggerSwizzler, NO,
204 [NSString stringWithFormat:@"I-SWZ%06ld",
205 (long)kGULSwizzlerMessageCodeAppDelegateSwizzling002],
206 @"AppDelegateProxy cannot create Interceptor ID.");
209 GULZeroingWeakContainer *weakObject = [[GULZeroingWeakContainer alloc] init];
210 weakObject.object = interceptor;
211 [GULAppDelegateSwizzler interceptors][interceptorID] = weakObject;
212 return interceptorID;
215 + (void)unregisterAppDelegateInterceptorWithID:(GULAppDelegateInterceptorID)interceptorID {
216 NSAssert(interceptorID, @"AppDelegateProxy cannot unregister nil interceptor ID.");
217 NSAssert(((NSString *)interceptorID).length != 0,
218 @"AppDelegateProxy cannot unregister empty interceptor ID.");
220 if (!interceptorID) {
221 GULLogError(kGULLoggerSwizzler, NO,
222 [NSString stringWithFormat:@"I-SWZ%06ld",
223 (long)kGULSwizzlerMessageCodeAppDelegateSwizzling003],
224 @"AppDelegateProxy cannot unregister empty interceptor ID.");
228 GULZeroingWeakContainer *weakContainer = [GULAppDelegateSwizzler interceptors][interceptorID];
229 if (!weakContainer.object) {
230 GULLogError(kGULLoggerSwizzler, NO,
231 [NSString stringWithFormat:@"I-SWZ%06ld",
232 (long)kGULSwizzlerMessageCodeAppDelegateSwizzling004],
233 @"AppDelegateProxy cannot unregister interceptor that was not registered. "
239 [[GULAppDelegateSwizzler interceptors] removeObjectForKey:interceptorID];
242 + (void)proxyOriginalDelegate {
243 dispatch_once(&sProxyAppDelegateOnceToken, ^{
244 id<UIApplicationDelegate> originalDelegate =
245 [GULAppDelegateSwizzler sharedApplication].delegate;
246 [GULAppDelegateSwizzler proxyAppDelegate:originalDelegate];
250 #pragma mark - Create proxy
252 + (UIApplication *)sharedApplication {
253 if ([GULAppEnvironmentUtil isAppExtension]) {
256 id sharedApplication = nil;
257 Class uiApplicationClass = NSClassFromString(@"UIApplication");
258 if (uiApplicationClass &&
259 [uiApplicationClass respondsToSelector:(NSSelectorFromString(@"sharedApplication"))]) {
260 sharedApplication = [uiApplicationClass sharedApplication];
262 return sharedApplication;
265 #pragma mark - Override default methods
267 /** Creates a new subclass of the class of the given object and sets the isa value of the given
268 * object to the new subclass. Additionally this copies methods to that new subclass that allow us
269 * to intercept UIApplicationDelegate methods. This is better known as isa swizzling.
271 * @param anObject The object to which you want to isa swizzle. This has to conform to the
272 * UIApplicationDelegate subclass.
274 + (void)createSubclassWithObject:(id<UIApplicationDelegate>)anObject {
275 Class realClass = [anObject class];
277 // Create GUL_<RealAppDelegate>_<timestampMs>
278 NSString *classNameWithPrefix =
279 [kGULAppDelegatePrefix stringByAppendingString:NSStringFromClass(realClass)];
280 NSTimeInterval timestamp = [NSDate date].timeIntervalSince1970;
281 NSString *newClassName =
282 [NSString stringWithFormat:@"%@-%0.0f", classNameWithPrefix, timestamp * 1000];
284 if (NSClassFromString(newClassName)) {
285 GULLogError(kGULLoggerSwizzler, NO,
286 [NSString stringWithFormat:@"I-SWZ%06ld",
287 (long)kGULSwizzlerMessageCodeAppDelegateSwizzling005],
288 @"Cannot create a proxy for App Delegate. Subclass already exists. Original Class: "
290 NSStringFromClass(realClass), newClassName);
294 // Register the new class as subclass of the real one. Do not allocate more than the real class
296 Class appDelegateSubClass = objc_allocateClassPair(realClass, newClassName.UTF8String, 0);
297 if (appDelegateSubClass == Nil) {
298 GULLogError(kGULLoggerSwizzler, NO,
299 [NSString stringWithFormat:@"I-SWZ%06ld",
300 (long)kGULSwizzlerMessageCodeAppDelegateSwizzling006],
301 @"Cannot create a proxy for App Delegate. Subclass already exists. Original Class: "
302 @"%@, subclass: Nil",
303 NSStringFromClass(realClass));
307 // Add the following methods from GULAppDelegate class, and store the real implementation so it
308 // can forward to the real one.
309 // For application:openURL:options:
310 NSValue *openURLOptionsIMPPointer;
311 SEL applicationOpenURLOptionsSEL = @selector(application:openURL:options:);
312 if ([anObject respondsToSelector:applicationOpenURLOptionsSEL]) {
313 // Only add the application:openURL:options: method if the original AppDelegate implements it.
314 // This fixes a bug if an app only implements application:openURL:sourceApplication:annotation:
315 // (if we add the `options` method, iOS sees that one exists and does not call the
316 // `sourceApplication` method, which in this case is the only one the app implements).
318 [GULAppDelegateSwizzler addInstanceMethodWithSelector:applicationOpenURLOptionsSEL
319 fromClass:[GULAppDelegateSwizzler class]
320 toClass:appDelegateSubClass];
321 GULRealOpenURLOptionsIMP openURLOptionsIMP = (GULRealOpenURLOptionsIMP)
322 [GULAppDelegateSwizzler implementationOfMethodSelector:applicationOpenURLOptionsSEL
323 fromClass:realClass];
324 openURLOptionsIMPPointer = [NSValue valueWithPointer:openURLOptionsIMP];
327 // For application:continueUserActivity:restorationHandler:
328 SEL continueUserActivitySEL = @selector(application:continueUserActivity:restorationHandler:);
329 [GULAppDelegateSwizzler addInstanceMethodWithSelector:continueUserActivitySEL
330 fromClass:[GULAppDelegateSwizzler class]
331 toClass:appDelegateSubClass];
332 GULRealContinueUserActivityIMP continueUserActivityIMP = (GULRealContinueUserActivityIMP)
333 [GULAppDelegateSwizzler implementationOfMethodSelector:continueUserActivitySEL
334 fromClass:realClass];
335 NSValue *continueUserActivityIMPPointer = [NSValue valueWithPointer:continueUserActivityIMP];
337 // For application:openURL:sourceApplication:annotation:
338 SEL openURLSourceApplicationAnnotationSEL =
339 @selector(application:openURL:sourceApplication:annotation:);
340 [GULAppDelegateSwizzler addInstanceMethodWithSelector:openURLSourceApplicationAnnotationSEL
341 fromClass:[GULAppDelegateSwizzler class]
342 toClass:appDelegateSubClass];
343 GULRealOpenURLSourceApplicationAnnotationIMP openURLSourceApplicationAnnotationIMP =
344 (GULRealOpenURLSourceApplicationAnnotationIMP)[GULAppDelegateSwizzler
345 implementationOfMethodSelector:openURLSourceApplicationAnnotationSEL
346 fromClass:realClass];
347 NSValue *openURLSourceAppAnnotationIMPPointer =
348 [NSValue valueWithPointer:openURLSourceApplicationAnnotationIMP];
350 // For application:handleEventsForBackgroundURLSession:completionHandler:
351 SEL handleEventsForBackgroundURLSessionSEL =
352 @selector(application:handleEventsForBackgroundURLSession:completionHandler:);
353 [GULAppDelegateSwizzler addInstanceMethodWithSelector:handleEventsForBackgroundURLSessionSEL
354 fromClass:[GULAppDelegateSwizzler class]
355 toClass:appDelegateSubClass];
356 GULRealHandleEventsForBackgroundURLSessionIMP handleBackgroundSessionIMP =
357 (GULRealHandleEventsForBackgroundURLSessionIMP)[GULAppDelegateSwizzler
358 implementationOfMethodSelector:handleEventsForBackgroundURLSessionSEL
359 fromClass:realClass];
360 NSValue *handleBackgroundSessionIMPPointer =
361 [NSValue valueWithPointer:handleBackgroundSessionIMP];
363 // Override the description too so the custom class name will not show up.
364 [GULAppDelegateSwizzler addInstanceMethodWithDestinationSelector:@selector(description)
365 withImplementationFromSourceSelector:@selector(fakeDescription)
366 fromClass:[self class]
367 toClass:appDelegateSubClass];
369 // Create fake properties for the real app delegate object.
370 objc_setAssociatedObject(anObject, &kGULContinueUserActivityIMPKey,
371 continueUserActivityIMPPointer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
372 objc_setAssociatedObject(anObject, &kGULHandleBackgroundSessionIMPKey,
373 handleBackgroundSessionIMPPointer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
374 if (openURLOptionsIMPPointer) {
375 objc_setAssociatedObject(anObject, &kGULOpenURLOptionsIMPKey, openURLOptionsIMPPointer,
376 OBJC_ASSOCIATION_RETAIN_NONATOMIC);
378 objc_setAssociatedObject(anObject, &kGULOpenURLOptionsSourceAnnotationsIMPKey,
379 openURLSourceAppAnnotationIMPPointer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
380 objc_setAssociatedObject(anObject, &kGULRealClassKey, realClass,
381 OBJC_ASSOCIATION_RETAIN_NONATOMIC);
383 // The subclass size has to be exactly the same size with the original class size. The subclass
384 // cannot have more ivars/properties than its superclass since it will cause an offset in memory
385 // that can lead to overwriting the isa of an object in the next frame.
386 if (class_getInstanceSize(realClass) != class_getInstanceSize(appDelegateSubClass)) {
387 GULLogError(kGULLoggerSwizzler, NO,
388 [NSString stringWithFormat:@"I-SWZ%06ld",
389 (long)kGULSwizzlerMessageCodeAppDelegateSwizzling007],
390 @"Cannot create subclass of App Delegate, because the created subclass is not the "
392 NSStringFromClass(realClass));
393 NSAssert(NO, @"Classes must be the same size to swizzle isa");
397 // Make the newly created class to be the subclass of the real App Delegate class.
398 objc_registerClassPair(appDelegateSubClass);
399 if (object_setClass(anObject, appDelegateSubClass)) {
400 GULLogDebug(kGULLoggerSwizzler, NO,
401 [NSString stringWithFormat:@"I-SWZ%06ld",
402 (long)kGULSwizzlerMessageCodeAppDelegateSwizzling008],
403 @"Successfully created App Delegate Proxy automatically. To disable the "
404 @"proxy, set the flag %@ to NO (Boolean) in the Info.plist",
405 [GULAppDelegateSwizzler correctAppDelegateProxyKey]);
408 // We have to do this to invalidate the cache that caches the original respondsToSelector of
409 // openURL handlers. Without this, it won't call the default implementations because the system
410 // checks and caches them.
411 // Register KVO only once. Otherwise, the observing method will be called as many times as
413 id<UIApplicationDelegate> delegate = [GULAppDelegateSwizzler sharedApplication].delegate;
414 [GULAppDelegateSwizzler sharedApplication].delegate = nil;
415 [GULAppDelegateSwizzler sharedApplication].delegate = delegate;
416 gOriginalAppDelegate = delegate;
417 [[GULAppDelegateObserver sharedInstance] observeUIApplication];
420 #pragma mark - Helper methods
422 + (GULMutableDictionary *)interceptors {
423 static dispatch_once_t onceToken;
424 static GULMutableDictionary *sInterceptors;
425 dispatch_once(&onceToken, ^{
426 sInterceptors = [[GULMutableDictionary alloc] init];
428 return sInterceptors;
431 /** Copies a method identified by the methodSelector from one class to the other. After this method
432 * is called, performing [toClassInstance methodSelector] will be similar to calling
433 * [fromClassInstance methodSelector]. This method does nothing if toClass already has a method
434 * identified by methodSelector.
436 * @param methodSelector The SEL that identifies both the method on the fromClass as well as the
437 * one on the toClass.
438 * @param fromClass The class from which a method is sourced.
439 * @param toClass The class to which the method is added. If the class already has a method with
440 * the same selector, this has no effect.
442 + (void)addInstanceMethodWithSelector:(SEL)methodSelector
443 fromClass:(Class)fromClass
444 toClass:(Class)toClass {
445 [self addInstanceMethodWithDestinationSelector:methodSelector
446 withImplementationFromSourceSelector:methodSelector
451 /** Copies a method identified by the sourceSelector from the fromClass as a method for the
452 * destinationSelector on the toClass. After this method is called, performing
453 * [toClassInstance destinationSelector] will be similar to calling
454 * [fromClassInstance sourceSelector]. This method does nothing if toClass already has a method
455 * identified by destinationSelector.
457 * @param destinationSelector The SEL that identifies the method on the toClass.
458 * @param sourceSelector The SEL that identifies the method on the fromClass.
459 * @param fromClass The class from which a method is sourced.
460 * @param toClass The class to which the method is added. If the class already has a method with
461 * the same selector, this has no effect.
463 + (void)addInstanceMethodWithDestinationSelector:(SEL)destinationSelector
464 withImplementationFromSourceSelector:(SEL)sourceSelector
465 fromClass:(Class)fromClass
466 toClass:(Class)toClass {
467 Method method = class_getInstanceMethod(fromClass, sourceSelector);
468 IMP methodIMP = method_getImplementation(method);
469 const char *types = method_getTypeEncoding(method);
470 if (!class_addMethod(toClass, destinationSelector, methodIMP, types)) {
471 GULLogWarning(kGULLoggerSwizzler, NO,
472 [NSString stringWithFormat:@"I-SWZ%06ld",
473 (long)kGULSwizzlerMessageCodeAppDelegateSwizzling009],
474 @"Cannot copy method to destination selector %@ as it already exists",
475 NSStringFromSelector(destinationSelector));
479 /** Gets the IMP of the instance method on the class identified by the selector.
481 * @param selector The selector of which the IMP is to be fetched.
482 * @param aClass The class from which the IMP is to be fetched.
483 * @return The IMP of the instance method identified by selector and aClass.
485 + (IMP)implementationOfMethodSelector:(SEL)selector fromClass:(Class)aClass {
486 Method aMethod = class_getInstanceMethod(aClass, selector);
487 return method_getImplementation(aMethod);
490 /** Enumerates through all the interceptors and if they respond to a given selector, executes a
491 * GULAppDelegateInterceptorCallback with the interceptor.
493 * @param methodSelector The SEL to check if an interceptor responds to.
494 * @param callback the GULAppDelegateInterceptorCallback.
496 + (void)notifyInterceptorsWithMethodSelector:(SEL)methodSelector
497 callback:(GULAppDelegateInterceptorCallback)callback {
502 NSDictionary *interceptors = [GULAppDelegateSwizzler interceptors].dictionary;
503 [interceptors enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
504 GULZeroingWeakContainer *interceptorContainer = obj;
505 id interceptor = interceptorContainer.object;
508 kGULLoggerSwizzler, NO,
510 stringWithFormat:@"I-SWZ%06ld", (long)kGULSwizzlerMessageCodeAppDelegateSwizzling010],
511 @"AppDelegateProxy cannot find interceptor with ID %@. Removing the interceptor.", key);
512 [[GULAppDelegateSwizzler interceptors] removeObjectForKey:key];
515 if ([interceptor respondsToSelector:methodSelector]) {
516 callback(interceptor);
521 // The methods below are donor methods which are added to the dynamic subclass of the App Delegate.
522 // They are called within the scope of the real App Delegate so |self| does not refer to the
523 // GULAppDelegateSwizzler instance but the real App Delegate instance.
525 #pragma mark - [Donor Methods] Overridden instance description method
527 - (NSString *)fakeDescription {
528 Class realClass = objc_getAssociatedObject(self, &kGULRealClassKey);
529 return [NSString stringWithFormat:@"<%@: %p>", realClass, self];
532 #pragma mark - [Donor Methods] URL overridden handler methods
534 - (BOOL)application:(UIApplication *)application
536 options:(NSDictionary<NSString *, id> *)options {
537 // Call the real implementation if the real App Delegate has any.
538 NSValue *openURLIMPPointer = objc_getAssociatedObject(self, &kGULOpenURLOptionsIMPKey);
539 GULRealOpenURLOptionsIMP openURLOptionsIMP = [openURLIMPPointer pointerValue];
541 __block BOOL returnedValue = NO;
542 SEL methodSelector = @selector(application:openURL:options:);
544 // This is needed to for the library to be warning free on iOS versions < 9.
545 #pragma clang diagnostic push
546 #pragma clang diagnostic ignored "-Wunguarded-availability"
547 [GULAppDelegateSwizzler
548 notifyInterceptorsWithMethodSelector:methodSelector
549 callback:^(id<UIApplicationDelegate> interceptor) {
550 returnedValue |= [interceptor application:application
554 #pragma clang diagnostic pop
555 if (openURLOptionsIMP) {
556 returnedValue |= openURLOptionsIMP(self, methodSelector, application, url, options);
558 return returnedValue;
561 - (BOOL)application:(UIApplication *)application
563 sourceApplication:(NSString *)sourceApplication
564 annotation:(id)annotation {
565 // Call the real implementation if the real App Delegate has any.
566 NSValue *openURLSourceAppAnnotationIMPPointer =
567 objc_getAssociatedObject(self, &kGULOpenURLOptionsSourceAnnotationsIMPKey);
568 GULRealOpenURLSourceApplicationAnnotationIMP openURLSourceApplicationAnnotationIMP =
569 [openURLSourceAppAnnotationIMPPointer pointerValue];
571 __block BOOL returnedValue = NO;
572 SEL methodSelector = @selector(application:openURL:sourceApplication:annotation:);
573 [GULAppDelegateSwizzler
574 notifyInterceptorsWithMethodSelector:methodSelector
575 callback:^(id<UIApplicationDelegate> interceptor) {
576 #pragma clang diagnostic push
577 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
578 returnedValue |= [interceptor application:application
580 sourceApplication:sourceApplication
581 annotation:annotation];
582 #pragma clang diagnostic pop
584 if (openURLSourceApplicationAnnotationIMP) {
585 returnedValue |= openURLSourceApplicationAnnotationIMP(self, methodSelector, application, url,
586 sourceApplication, annotation);
588 return returnedValue;
591 #pragma mark - [Donor Methods] Network overridden handler methods
593 #pragma clang diagnostic push
594 #pragma clang diagnostic ignored "-Wstrict-prototypes"
595 - (void)application:(UIApplication *)application
596 handleEventsForBackgroundURLSession:(NSString *)identifier
597 completionHandler:(void (^)())completionHandler API_AVAILABLE(ios(7.0)) {
598 #pragma clang diagnostic pop
599 NSValue *handleBackgroundSessionPointer =
600 objc_getAssociatedObject(self, &kGULHandleBackgroundSessionIMPKey);
601 GULRealHandleEventsForBackgroundURLSessionIMP handleBackgroundSessionIMP =
602 [handleBackgroundSessionPointer pointerValue];
604 // Notify interceptors.
606 @selector(application:handleEventsForBackgroundURLSession:completionHandler:);
607 [GULAppDelegateSwizzler
608 notifyInterceptorsWithMethodSelector:methodSelector
609 callback:^(id<UIApplicationDelegate> interceptor) {
610 [interceptor application:application
611 handleEventsForBackgroundURLSession:identifier
612 completionHandler:completionHandler];
614 // Call the real implementation if the real App Delegate has any.
615 if (handleBackgroundSessionIMP) {
616 handleBackgroundSessionIMP(self, methodSelector, application, identifier, completionHandler);
620 #pragma mark - [Donor Methods] User Activities overridden handler methods
622 // This is needed to for the library to be warning free on iOS versions < 8.
623 #pragma clang diagnostic push
624 #pragma clang diagnostic ignored "-Wunguarded-availability"
625 - (BOOL)application:(UIApplication *)application
626 continueUserActivity:(NSUserActivity *)userActivity
627 restorationHandler:(void (^)(NSArray *restorableObjects))restorationHandler {
628 NSValue *continueUserActivityIMPPointer =
629 objc_getAssociatedObject(self, &kGULContinueUserActivityIMPKey);
630 GULRealContinueUserActivityIMP continueUserActivityIMP =
631 continueUserActivityIMPPointer.pointerValue;
633 __block BOOL returnedValue = NO;
634 SEL methodSelector = @selector(application:continueUserActivity:restorationHandler:);
635 [GULAppDelegateSwizzler
636 notifyInterceptorsWithMethodSelector:methodSelector
637 callback:^(id<UIApplicationDelegate> interceptor) {
638 returnedValue |= [interceptor application:application
639 continueUserActivity:userActivity
640 restorationHandler:restorationHandler];
642 // Call the real implementation if the real App Delegate has any.
643 if (continueUserActivityIMP) {
644 returnedValue |= continueUserActivityIMP(self, methodSelector, application, userActivity,
647 return returnedValue;
649 #pragma clang diagnostic pop
651 + (void)proxyAppDelegate:(id<UIApplicationDelegate>)appDelegate {
652 id<UIApplicationDelegate> originalDelegate = appDelegate;
653 // Do not create a subclass if it is not enabled.
654 if (![GULAppDelegateSwizzler isAppDelegateProxyEnabled]) {
655 GULLogNotice(kGULLoggerSwizzler, NO,
656 [NSString stringWithFormat:@"I-SWZ%06ld",
657 (long)kGULSwizzlerMessageCodeAppDelegateSwizzling011],
658 @"App Delegate Proxy is disabled. %@",
659 [GULAppDelegateSwizzler correctAlternativeWhenAppDelegateProxyNotCreated]);
662 // Do not accept nil delegate.
663 if (!originalDelegate) {
664 GULLogError(kGULLoggerSwizzler, NO,
665 [NSString stringWithFormat:@"I-SWZ%06ld",
666 (long)kGULSwizzlerMessageCodeAppDelegateSwizzling012],
667 @"Cannot create App Delegate Proxy because App Delegate instance is nil. %@",
668 [GULAppDelegateSwizzler correctAlternativeWhenAppDelegateProxyNotCreated]);
673 [self createSubclassWithObject:originalDelegate];
674 } @catch (NSException *exception) {
675 GULLogError(kGULLoggerSwizzler, NO,
676 [NSString stringWithFormat:@"I-SWZ%06ld",
677 (long)kGULSwizzlerMessageCodeAppDelegateSwizzling013],
678 @"Cannot create App Delegate Proxy. %@",
679 [GULAppDelegateSwizzler correctAlternativeWhenAppDelegateProxyNotCreated]);
684 #pragma mark - Methods to print correct debug logs
686 + (NSString *)correctAppDelegateProxyKey {
687 return NSClassFromString(@"FIRCore") ? kGULFirebaseAppDelegateProxyEnabledPlistKey
688 : kGULGoogleUtilitiesAppDelegateProxyEnabledPlistKey;
691 + (NSString *)correctAlternativeWhenAppDelegateProxyNotCreated {
692 return NSClassFromString(@"FIRCore")
693 ? @"To log deep link campaigns manually, call the methods in "
694 @"FIRAnalytics+AppDelegate.h."
698 #pragma mark - Private Methods for Testing
700 #ifdef GUL_APP_DELEGATE_TESTING
702 + (void)clearInterceptors {
703 [[self interceptors] removeAllObjects];
706 + (void)resetProxyOriginalDelegateOnceToken {
707 sProxyAppDelegateOnceToken = 0;
710 + (id<UIApplicationDelegate>)originalDelegate {
711 return gOriginalAppDelegate;
714 #endif // GUL_APP_DELEGATE_TESTING
718 #endif // TARGET_OS_IOS