added iOS source code
[wl-app.git] / iOS / Pods / GoogleUtilities / GoogleUtilities / AppDelegateSwizzler / GULAppDelegateSwizzler.m
1 // Copyright 2018 Google LLC
2 //
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
6 //
7 //      http://www.apache.org/licenses/LICENSE-2.0
8 //
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.
14
15 #import "TargetConditionals.h"
16
17 #if TARGET_OS_IOS
18
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"
25
26 #import <UIKit/UIKit.h>
27 #import <objc/runtime.h>
28
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);
33
34 typedef BOOL (*GULRealOpenURLOptionsIMP)(
35     id, SEL, UIApplication *, NSURL *, NSDictionary<NSString *, id> *);
36
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
42
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
49
50 typedef void (^GULAppDelegateInterceptorCallback)(id<UIApplicationDelegate>);
51
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";
60
61 static GULLoggerService kGULLoggerSwizzler = @"[GoogleUtilities/AppDelegateSwizzler]";
62
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.
65
66 /** Plist key that allows Firebase developers to disable App Delegate Proxying. */
67 static NSString *const kGULFirebaseAppDelegateProxyEnabledPlistKey =
68     @"FirebaseAppDelegateProxyEnabled";
69
70 /** Plist key that allows developers not using Firebase to disable App Delegate Proxying. */
71 static NSString *const kGULGoogleUtilitiesAppDelegateProxyEnabledPlistKey =
72     @"GoogleUtilitiesAppDelegateProxyEnabled";
73
74 /** The prefix of the App Delegate. */
75 static NSString *const kGULAppDelegatePrefix = @"GUL_";
76
77 /** The original instance of App Delegate. */
78 static id<UIApplicationDelegate> gOriginalAppDelegate;
79
80 /**
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.
86  */
87 @interface GULZeroingWeakContainer : NSObject
88
89 /** Stores a weak object. */
90 @property(nonatomic, weak) id object;
91
92 @end
93
94 @implementation GULZeroingWeakContainer
95 @end
96
97 @interface GULAppDelegateObserver : NSObject
98 @end
99
100 @implementation GULAppDelegateObserver {
101   BOOL _isObserving;
102 }
103
104 + (GULAppDelegateObserver *)sharedInstance {
105   static GULAppDelegateObserver *instance;
106   static dispatch_once_t once;
107   dispatch_once(&once, ^{
108     instance = [[GULAppDelegateObserver alloc] init];
109   });
110   return instance;
111 }
112
113 - (void)observeUIApplication {
114   if (_isObserving) {
115     return;
116   }
117   [[GULAppDelegateSwizzler sharedApplication]
118       addObserver:self
119        forKeyPath:kGULAppDelegateKeyPath
120           options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
121           context:nil];
122   _isObserving = YES;
123 }
124
125 - (void)observeValueForKeyPath:(NSString *)keyPath
126                       ofObject:(id)object
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]) {
133       return;
134     }
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];
142       _isObserving = NO;
143     }
144   }
145 }
146
147 @end
148
149 @implementation GULAppDelegateSwizzler
150
151 static dispatch_once_t sProxyAppDelegateOnceToken;
152
153 #pragma mark - Public methods
154
155 + (BOOL)isAppDelegateProxyEnabled {
156   NSDictionary *infoDictionary = [NSBundle mainBundle].infoDictionary;
157
158   id isFirebaseProxyEnabledPlistValue = infoDictionary[kGULFirebaseAppDelegateProxyEnabledPlistKey];
159   id isGoogleProxyEnabledPlistValue =
160       infoDictionary[kGULGoogleUtilitiesAppDelegateProxyEnabledPlistKey];
161
162   // Enabled by default.
163   BOOL isFirebaseAppDelegateProxyEnabled = YES;
164   BOOL isGoogleUtilitiesAppDelegateProxyEnabled = YES;
165
166   if ([isFirebaseProxyEnabledPlistValue isKindOfClass:[NSNumber class]]) {
167     isFirebaseAppDelegateProxyEnabled = [isFirebaseProxyEnabledPlistValue boolValue];
168   }
169
170   if ([isGoogleProxyEnabledPlistValue isKindOfClass:[NSNumber class]]) {
171     isGoogleUtilitiesAppDelegateProxyEnabled = [isGoogleProxyEnabledPlistValue boolValue];
172   }
173
174   // Only deactivate the proxy if it is explicitly disabled by app developers using either one of
175   // the plist flags.
176   return isFirebaseAppDelegateProxyEnabled && isGoogleUtilitiesAppDelegateProxyEnabled;
177 }
178
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");
184
185   if (!interceptor) {
186     GULLogError(kGULLoggerSwizzler, NO,
187                 [NSString stringWithFormat:@"I-SWZ%06ld",
188                                            (long)kGULSwizzlerMessageCodeAppDelegateSwizzling000],
189                 @"AppDelegateProxy cannot add nil interceptor.");
190     return nil;
191   }
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");
197     return nil;
198   }
199
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.");
207     return nil;
208   }
209   GULZeroingWeakContainer *weakObject = [[GULZeroingWeakContainer alloc] init];
210   weakObject.object = interceptor;
211   [GULAppDelegateSwizzler interceptors][interceptorID] = weakObject;
212   return interceptorID;
213 }
214
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.");
219
220   if (!interceptorID) {
221     GULLogError(kGULLoggerSwizzler, NO,
222                 [NSString stringWithFormat:@"I-SWZ%06ld",
223                                            (long)kGULSwizzlerMessageCodeAppDelegateSwizzling003],
224                 @"AppDelegateProxy cannot unregister empty interceptor ID.");
225     return;
226   }
227
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. "
234                  "Interceptor ID %@",
235                 interceptorID);
236     return;
237   }
238
239   [[GULAppDelegateSwizzler interceptors] removeObjectForKey:interceptorID];
240 }
241
242 + (void)proxyOriginalDelegate {
243   dispatch_once(&sProxyAppDelegateOnceToken, ^{
244     id<UIApplicationDelegate> originalDelegate =
245         [GULAppDelegateSwizzler sharedApplication].delegate;
246     [GULAppDelegateSwizzler proxyAppDelegate:originalDelegate];
247   });
248 }
249
250 #pragma mark - Create proxy
251
252 + (UIApplication *)sharedApplication {
253   if ([GULAppEnvironmentUtil isAppExtension]) {
254     return nil;
255   }
256   id sharedApplication = nil;
257   Class uiApplicationClass = NSClassFromString(@"UIApplication");
258   if (uiApplicationClass &&
259       [uiApplicationClass respondsToSelector:(NSSelectorFromString(@"sharedApplication"))]) {
260     sharedApplication = [uiApplicationClass sharedApplication];
261   }
262   return sharedApplication;
263 }
264
265 #pragma mark - Override default methods
266
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.
270  *
271  *  @param anObject The object to which you want to isa swizzle. This has to conform to the
272  *      UIApplicationDelegate subclass.
273  */
274 + (void)createSubclassWithObject:(id<UIApplicationDelegate>)anObject {
275   Class realClass = [anObject class];
276
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];
283
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: "
289                 @"%@, subclass: %@",
290                 NSStringFromClass(realClass), newClassName);
291     return;
292   }
293
294   // Register the new class as subclass of the real one. Do not allocate more than the real class
295   // size.
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));
304     return;
305   }
306
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).
317
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];
325   }
326
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];
336
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];
349
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];
362
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];
368
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);
377   }
378   objc_setAssociatedObject(anObject, &kGULOpenURLOptionsSourceAnnotationsIMPKey,
379                            openURLSourceAppAnnotationIMPPointer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
380   objc_setAssociatedObject(anObject, &kGULRealClassKey, realClass,
381                            OBJC_ASSOCIATION_RETAIN_NONATOMIC);
382
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 "
391                 @"same size. %@",
392                 NSStringFromClass(realClass));
393     NSAssert(NO, @"Classes must be the same size to swizzle isa");
394     return;
395   }
396
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]);
406   }
407
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
412   // being registered.
413   id<UIApplicationDelegate> delegate = [GULAppDelegateSwizzler sharedApplication].delegate;
414   [GULAppDelegateSwizzler sharedApplication].delegate = nil;
415   [GULAppDelegateSwizzler sharedApplication].delegate = delegate;
416   gOriginalAppDelegate = delegate;
417   [[GULAppDelegateObserver sharedInstance] observeUIApplication];
418 }
419
420 #pragma mark - Helper methods
421
422 + (GULMutableDictionary *)interceptors {
423   static dispatch_once_t onceToken;
424   static GULMutableDictionary *sInterceptors;
425   dispatch_once(&onceToken, ^{
426     sInterceptors = [[GULMutableDictionary alloc] init];
427   });
428   return sInterceptors;
429 }
430
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.
435  *
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.
441  */
442 + (void)addInstanceMethodWithSelector:(SEL)methodSelector
443                             fromClass:(Class)fromClass
444                               toClass:(Class)toClass {
445   [self addInstanceMethodWithDestinationSelector:methodSelector
446             withImplementationFromSourceSelector:methodSelector
447                                        fromClass:fromClass
448                                          toClass:toClass];
449 }
450
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.
456  *
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.
462  */
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));
476   }
477 }
478
479 /** Gets the IMP of the instance method on the class identified by the selector.
480  *
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.
484  */
485 + (IMP)implementationOfMethodSelector:(SEL)selector fromClass:(Class)aClass {
486   Method aMethod = class_getInstanceMethod(aClass, selector);
487   return method_getImplementation(aMethod);
488 }
489
490 /** Enumerates through all the interceptors and if they respond to a given selector, executes a
491  *  GULAppDelegateInterceptorCallback with the interceptor.
492  *
493  *  @param methodSelector The SEL to check if an interceptor responds to.
494  *  @param callback the GULAppDelegateInterceptorCallback.
495  */
496 + (void)notifyInterceptorsWithMethodSelector:(SEL)methodSelector
497                                     callback:(GULAppDelegateInterceptorCallback)callback {
498   if (!callback) {
499     return;
500   }
501
502   NSDictionary *interceptors = [GULAppDelegateSwizzler interceptors].dictionary;
503   [interceptors enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
504     GULZeroingWeakContainer *interceptorContainer = obj;
505     id interceptor = interceptorContainer.object;
506     if (!interceptor) {
507       GULLogWarning(
508           kGULLoggerSwizzler, NO,
509           [NSString
510               stringWithFormat:@"I-SWZ%06ld", (long)kGULSwizzlerMessageCodeAppDelegateSwizzling010],
511           @"AppDelegateProxy cannot find interceptor with ID %@. Removing the interceptor.", key);
512       [[GULAppDelegateSwizzler interceptors] removeObjectForKey:key];
513       return;
514     }
515     if ([interceptor respondsToSelector:methodSelector]) {
516       callback(interceptor);
517     }
518   }];
519 }
520
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.
524
525 #pragma mark - [Donor Methods] Overridden instance description method
526
527 - (NSString *)fakeDescription {
528   Class realClass = objc_getAssociatedObject(self, &kGULRealClassKey);
529   return [NSString stringWithFormat:@"<%@: %p>", realClass, self];
530 }
531
532 #pragma mark - [Donor Methods] URL overridden handler methods
533
534 - (BOOL)application:(UIApplication *)application
535             openURL:(NSURL *)url
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];
540
541   __block BOOL returnedValue = NO;
542   SEL methodSelector = @selector(application:openURL:options:);
543
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
551                                                                       openURL:url
552                                                                       options:options];
553                                   }];
554 #pragma clang diagnostic pop
555   if (openURLOptionsIMP) {
556     returnedValue |= openURLOptionsIMP(self, methodSelector, application, url, options);
557   }
558   return returnedValue;
559 }
560
561 - (BOOL)application:(UIApplication *)application
562               openURL:(NSURL *)url
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];
570
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
579                                                                       openURL:url
580                                                             sourceApplication:sourceApplication
581                                                                    annotation:annotation];
582 #pragma clang diagnostic pop
583                                   }];
584   if (openURLSourceApplicationAnnotationIMP) {
585     returnedValue |= openURLSourceApplicationAnnotationIMP(self, methodSelector, application, url,
586                                                            sourceApplication, annotation);
587   }
588   return returnedValue;
589 }
590
591 #pragma mark - [Donor Methods] Network overridden handler methods
592
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];
603
604   // Notify interceptors.
605   SEL methodSelector =
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];
613                                   }];
614   // Call the real implementation if the real App Delegate has any.
615   if (handleBackgroundSessionIMP) {
616     handleBackgroundSessionIMP(self, methodSelector, application, identifier, completionHandler);
617   }
618 }
619
620 #pragma mark - [Donor Methods] User Activities overridden handler methods
621
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;
632
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];
641                                   }];
642   // Call the real implementation if the real App Delegate has any.
643   if (continueUserActivityIMP) {
644     returnedValue |= continueUserActivityIMP(self, methodSelector, application, userActivity,
645                                              restorationHandler);
646   }
647   return returnedValue;
648 }
649 #pragma clang diagnostic pop
650
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]);
660     return;
661   }
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]);
669     return;
670   }
671
672   @try {
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]);
680     return;
681   }
682 }
683
684 #pragma mark - Methods to print correct debug logs
685
686 + (NSString *)correctAppDelegateProxyKey {
687   return NSClassFromString(@"FIRCore") ? kGULFirebaseAppDelegateProxyEnabledPlistKey
688                                        : kGULGoogleUtilitiesAppDelegateProxyEnabledPlistKey;
689 }
690
691 + (NSString *)correctAlternativeWhenAppDelegateProxyNotCreated {
692   return NSClassFromString(@"FIRCore")
693              ? @"To log deep link campaigns manually, call the methods in "
694                @"FIRAnalytics+AppDelegate.h."
695              : @"";
696 }
697
698 #pragma mark - Private Methods for Testing
699
700 #ifdef GUL_APP_DELEGATE_TESTING
701
702 + (void)clearInterceptors {
703   [[self interceptors] removeAllObjects];
704 }
705
706 + (void)resetProxyOriginalDelegateOnceToken {
707   sProxyAppDelegateOnceToken = 0;
708 }
709
710 + (id<UIApplicationDelegate>)originalDelegate {
711   return gOriginalAppDelegate;
712 }
713
714 #endif  // GUL_APP_DELEGATE_TESTING
715
716 @end
717
718 #endif  // TARGET_OS_IOS