1 |
efrain |
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 |
#import "GoogleUtilities/AppDelegateSwizzler/Internal/GULAppDelegateSwizzler_Private.h"
|
|
|
18 |
#import "GoogleUtilities/AppDelegateSwizzler/Public/GoogleUtilities/GULAppDelegateSwizzler.h"
|
|
|
19 |
#import "GoogleUtilities/Common/GULLoggerCodes.h"
|
|
|
20 |
#import "GoogleUtilities/Environment/Public/GoogleUtilities/GULAppEnvironmentUtil.h"
|
|
|
21 |
#import "GoogleUtilities/Logger/Public/GoogleUtilities/GULLogger.h"
|
|
|
22 |
#import "GoogleUtilities/Network/Public/GoogleUtilities/GULMutableDictionary.h"
|
|
|
23 |
|
|
|
24 |
#import <dispatch/group.h>
|
|
|
25 |
#import <objc/runtime.h>
|
|
|
26 |
|
|
|
27 |
// Implementations need to be typed before calling the implementation directly to cast the
|
|
|
28 |
// arguments and the return types correctly. Otherwise, it will crash the app.
|
|
|
29 |
typedef BOOL (*GULRealOpenURLSourceApplicationAnnotationIMP)(
|
|
|
30 |
id, SEL, GULApplication *, NSURL *, NSString *, id);
|
|
|
31 |
|
|
|
32 |
typedef BOOL (*GULRealOpenURLOptionsIMP)(
|
|
|
33 |
id, SEL, GULApplication *, NSURL *, NSDictionary<NSString *, id> *);
|
|
|
34 |
|
|
|
35 |
#pragma clang diagnostic push
|
|
|
36 |
#pragma clang diagnostic ignored "-Wstrict-prototypes"
|
|
|
37 |
typedef void (*GULRealHandleEventsForBackgroundURLSessionIMP)(
|
|
|
38 |
id, SEL, GULApplication *, NSString *, void (^)());
|
|
|
39 |
#pragma clang diagnostic pop
|
|
|
40 |
|
|
|
41 |
typedef BOOL (*GULRealContinueUserActivityIMP)(
|
|
|
42 |
id, SEL, GULApplication *, NSUserActivity *, void (^)(NSArray *restorableObjects));
|
|
|
43 |
|
|
|
44 |
typedef void (*GULRealDidRegisterForRemoteNotificationsIMP)(id, SEL, GULApplication *, NSData *);
|
|
|
45 |
|
|
|
46 |
typedef void (*GULRealDidFailToRegisterForRemoteNotificationsIMP)(id,
|
|
|
47 |
SEL,
|
|
|
48 |
GULApplication *,
|
|
|
49 |
NSError *);
|
|
|
50 |
|
|
|
51 |
typedef void (*GULRealDidReceiveRemoteNotificationIMP)(id, SEL, GULApplication *, NSDictionary *);
|
|
|
52 |
|
|
|
53 |
#if !TARGET_OS_WATCH && !TARGET_OS_OSX
|
|
|
54 |
typedef void (*GULRealDidReceiveRemoteNotificationWithCompletionIMP)(
|
|
|
55 |
id, SEL, GULApplication *, NSDictionary *, void (^)(UIBackgroundFetchResult));
|
|
|
56 |
#endif // !TARGET_OS_WATCH && !TARGET_OS_OSX
|
|
|
57 |
|
|
|
58 |
typedef void (^GULAppDelegateInterceptorCallback)(id<GULApplicationDelegate>);
|
|
|
59 |
|
|
|
60 |
// The strings below are the keys for associated objects.
|
|
|
61 |
static char const *const kGULRealIMPBySelectorKey = "GUL_realIMPBySelector";
|
|
|
62 |
static char const *const kGULRealClassKey = "GUL_realClass";
|
|
|
63 |
|
|
|
64 |
static NSString *const kGULAppDelegateKeyPath = @"delegate";
|
|
|
65 |
|
|
|
66 |
static GULLoggerService kGULLoggerSwizzler = @"[GoogleUtilities/AppDelegateSwizzler]";
|
|
|
67 |
|
|
|
68 |
// Since Firebase SDKs also use this for app delegate proxying, in order to not be a breaking change
|
|
|
69 |
// we disable App Delegate proxying when either of these two flags are set to NO.
|
|
|
70 |
|
|
|
71 |
/** Plist key that allows Firebase developers to disable App and Scene Delegate Proxying. */
|
|
|
72 |
static NSString *const kGULFirebaseAppDelegateProxyEnabledPlistKey =
|
|
|
73 |
@"FirebaseAppDelegateProxyEnabled";
|
|
|
74 |
|
|
|
75 |
/** Plist key that allows developers not using Firebase to disable App and Scene Delegate Proxying.
|
|
|
76 |
*/
|
|
|
77 |
static NSString *const kGULGoogleUtilitiesAppDelegateProxyEnabledPlistKey =
|
|
|
78 |
@"GoogleUtilitiesAppDelegateProxyEnabled";
|
|
|
79 |
|
|
|
80 |
/** The prefix of the App Delegate. */
|
|
|
81 |
static NSString *const kGULAppDelegatePrefix = @"GUL_";
|
|
|
82 |
|
|
|
83 |
/** The original instance of App Delegate. */
|
|
|
84 |
static id<GULApplicationDelegate> gOriginalAppDelegate;
|
|
|
85 |
|
|
|
86 |
/** The original App Delegate class */
|
|
|
87 |
static Class gOriginalAppDelegateClass;
|
|
|
88 |
|
|
|
89 |
/** The subclass of the original App Delegate. */
|
|
|
90 |
static Class gAppDelegateSubclass;
|
|
|
91 |
|
|
|
92 |
/** Remote notification methods selectors
|
|
|
93 |
*
|
|
|
94 |
* We have to opt out of referencing APNS related App Delegate methods directly to prevent
|
|
|
95 |
* an Apple review warning email about missing Push Notification Entitlement
|
|
|
96 |
* (like here: https://github.com/firebase/firebase-ios-sdk/issues/2807). From our experience, the
|
|
|
97 |
* warning is triggered when any of the symbols is present in the application sent to review, even
|
|
|
98 |
* if the code is never executed. Because GULAppDelegateSwizzler may be used by applications that
|
|
|
99 |
* are not using APNS we have to refer to the methods indirectly using selector constructed from
|
|
|
100 |
* string.
|
|
|
101 |
*
|
|
|
102 |
* NOTE: None of the methods is proxied unless it is explicitly requested by calling the method
|
|
|
103 |
* +[GULAppDelegateSwizzler proxyOriginalDelegateIncludingAPNSMethods]
|
|
|
104 |
*/
|
|
|
105 |
static NSString *const kGULDidRegisterForRemoteNotificationsSEL =
|
|
|
106 |
@"application:didRegisterForRemoteNotificationsWithDeviceToken:";
|
|
|
107 |
static NSString *const kGULDidFailToRegisterForRemoteNotificationsSEL =
|
|
|
108 |
@"application:didFailToRegisterForRemoteNotificationsWithError:";
|
|
|
109 |
static NSString *const kGULDidReceiveRemoteNotificationSEL =
|
|
|
110 |
@"application:didReceiveRemoteNotification:";
|
|
|
111 |
static NSString *const kGULDidReceiveRemoteNotificationWithCompletionSEL =
|
|
|
112 |
@"application:didReceiveRemoteNotification:fetchCompletionHandler:";
|
|
|
113 |
|
|
|
114 |
/**
|
|
|
115 |
* This class is necessary to store the delegates in an NSArray without retaining them.
|
|
|
116 |
* [NSValue valueWithNonRetainedObject] also provides this functionality, but does not provide a
|
|
|
117 |
* zeroing pointer. This will cause EXC_BAD_ACCESS when trying to access the object after it is
|
|
|
118 |
* dealloced. Instead, this container stores a weak, zeroing reference to the object, which
|
|
|
119 |
* automatically is set to nil by the runtime when the object is dealloced.
|
|
|
120 |
*/
|
|
|
121 |
@interface GULZeroingWeakContainer : NSObject
|
|
|
122 |
|
|
|
123 |
/** Stores a weak object. */
|
|
|
124 |
@property(nonatomic, weak) id object;
|
|
|
125 |
|
|
|
126 |
@end
|
|
|
127 |
|
|
|
128 |
@implementation GULZeroingWeakContainer
|
|
|
129 |
@end
|
|
|
130 |
|
|
|
131 |
@interface GULAppDelegateObserver : NSObject
|
|
|
132 |
@end
|
|
|
133 |
|
|
|
134 |
@implementation GULAppDelegateObserver {
|
|
|
135 |
BOOL _isObserving;
|
|
|
136 |
}
|
|
|
137 |
|
|
|
138 |
+ (GULAppDelegateObserver *)sharedInstance {
|
|
|
139 |
static GULAppDelegateObserver *instance;
|
|
|
140 |
static dispatch_once_t once;
|
|
|
141 |
dispatch_once(&once, ^{
|
|
|
142 |
instance = [[GULAppDelegateObserver alloc] init];
|
|
|
143 |
});
|
|
|
144 |
return instance;
|
|
|
145 |
}
|
|
|
146 |
|
|
|
147 |
- (void)observeUIApplication {
|
|
|
148 |
if (_isObserving) {
|
|
|
149 |
return;
|
|
|
150 |
}
|
|
|
151 |
[[GULAppDelegateSwizzler sharedApplication]
|
|
|
152 |
addObserver:self
|
|
|
153 |
forKeyPath:kGULAppDelegateKeyPath
|
|
|
154 |
options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
|
|
|
155 |
context:nil];
|
|
|
156 |
_isObserving = YES;
|
|
|
157 |
}
|
|
|
158 |
|
|
|
159 |
- (void)observeValueForKeyPath:(NSString *)keyPath
|
|
|
160 |
ofObject:(id)object
|
|
|
161 |
change:(NSDictionary *)change
|
|
|
162 |
context:(void *)context {
|
|
|
163 |
if ([keyPath isEqual:kGULAppDelegateKeyPath]) {
|
|
|
164 |
id newValue = change[NSKeyValueChangeNewKey];
|
|
|
165 |
id oldValue = change[NSKeyValueChangeOldKey];
|
|
|
166 |
if ([newValue isEqual:oldValue]) {
|
|
|
167 |
return;
|
|
|
168 |
}
|
|
|
169 |
// Free the stored app delegate instance because it has been changed to a different instance to
|
|
|
170 |
// avoid keeping it alive forever.
|
|
|
171 |
if ([oldValue isEqual:gOriginalAppDelegate]) {
|
|
|
172 |
gOriginalAppDelegate = nil;
|
|
|
173 |
// Remove the observer. Parse it to NSObject to avoid warning.
|
|
|
174 |
[[GULAppDelegateSwizzler sharedApplication] removeObserver:self
|
|
|
175 |
forKeyPath:kGULAppDelegateKeyPath];
|
|
|
176 |
_isObserving = NO;
|
|
|
177 |
}
|
|
|
178 |
}
|
|
|
179 |
}
|
|
|
180 |
|
|
|
181 |
@end
|
|
|
182 |
|
|
|
183 |
@implementation GULAppDelegateSwizzler
|
|
|
184 |
|
|
|
185 |
static dispatch_once_t sProxyAppDelegateOnceToken;
|
|
|
186 |
static dispatch_once_t sProxyAppDelegateRemoteNotificationOnceToken;
|
|
|
187 |
|
|
|
188 |
#pragma mark - Public methods
|
|
|
189 |
|
|
|
190 |
+ (BOOL)isAppDelegateProxyEnabled {
|
|
|
191 |
NSDictionary *infoDictionary = [NSBundle mainBundle].infoDictionary;
|
|
|
192 |
|
|
|
193 |
id isFirebaseProxyEnabledPlistValue = infoDictionary[kGULFirebaseAppDelegateProxyEnabledPlistKey];
|
|
|
194 |
id isGoogleProxyEnabledPlistValue =
|
|
|
195 |
infoDictionary[kGULGoogleUtilitiesAppDelegateProxyEnabledPlistKey];
|
|
|
196 |
|
|
|
197 |
// Enabled by default.
|
|
|
198 |
BOOL isFirebaseAppDelegateProxyEnabled = YES;
|
|
|
199 |
BOOL isGoogleUtilitiesAppDelegateProxyEnabled = YES;
|
|
|
200 |
|
|
|
201 |
if ([isFirebaseProxyEnabledPlistValue isKindOfClass:[NSNumber class]]) {
|
|
|
202 |
isFirebaseAppDelegateProxyEnabled = [isFirebaseProxyEnabledPlistValue boolValue];
|
|
|
203 |
}
|
|
|
204 |
|
|
|
205 |
if ([isGoogleProxyEnabledPlistValue isKindOfClass:[NSNumber class]]) {
|
|
|
206 |
isGoogleUtilitiesAppDelegateProxyEnabled = [isGoogleProxyEnabledPlistValue boolValue];
|
|
|
207 |
}
|
|
|
208 |
|
|
|
209 |
// Only deactivate the proxy if it is explicitly disabled by app developers using either one of
|
|
|
210 |
// the plist flags.
|
|
|
211 |
return isFirebaseAppDelegateProxyEnabled && isGoogleUtilitiesAppDelegateProxyEnabled;
|
|
|
212 |
}
|
|
|
213 |
|
|
|
214 |
+ (GULAppDelegateInterceptorID)registerAppDelegateInterceptor:
|
|
|
215 |
(id<GULApplicationDelegate>)interceptor {
|
|
|
216 |
NSAssert(interceptor, @"AppDelegateProxy cannot add nil interceptor");
|
|
|
217 |
NSAssert([interceptor conformsToProtocol:@protocol(GULApplicationDelegate)],
|
|
|
218 |
@"AppDelegateProxy interceptor does not conform to UIApplicationDelegate");
|
|
|
219 |
|
|
|
220 |
if (!interceptor) {
|
|
|
221 |
GULLogError(kGULLoggerSwizzler, NO,
|
|
|
222 |
[NSString stringWithFormat:@"I-SWZ%06ld",
|
|
|
223 |
(long)kGULSwizzlerMessageCodeAppDelegateSwizzling000],
|
|
|
224 |
@"AppDelegateProxy cannot add nil interceptor.");
|
|
|
225 |
return nil;
|
|
|
226 |
}
|
|
|
227 |
if (![interceptor conformsToProtocol:@protocol(GULApplicationDelegate)]) {
|
|
|
228 |
GULLogError(kGULLoggerSwizzler, NO,
|
|
|
229 |
[NSString stringWithFormat:@"I-SWZ%06ld",
|
|
|
230 |
(long)kGULSwizzlerMessageCodeAppDelegateSwizzling001],
|
|
|
231 |
@"AppDelegateProxy interceptor does not conform to UIApplicationDelegate");
|
|
|
232 |
return nil;
|
|
|
233 |
}
|
|
|
234 |
|
|
|
235 |
// The ID should be the same given the same interceptor object.
|
|
|
236 |
NSString *interceptorID = [NSString stringWithFormat:@"%@%p", kGULAppDelegatePrefix, interceptor];
|
|
|
237 |
if (!interceptorID.length) {
|
|
|
238 |
GULLogError(kGULLoggerSwizzler, NO,
|
|
|
239 |
[NSString stringWithFormat:@"I-SWZ%06ld",
|
|
|
240 |
(long)kGULSwizzlerMessageCodeAppDelegateSwizzling002],
|
|
|
241 |
@"AppDelegateProxy cannot create Interceptor ID.");
|
|
|
242 |
return nil;
|
|
|
243 |
}
|
|
|
244 |
GULZeroingWeakContainer *weakObject = [[GULZeroingWeakContainer alloc] init];
|
|
|
245 |
weakObject.object = interceptor;
|
|
|
246 |
[GULAppDelegateSwizzler interceptors][interceptorID] = weakObject;
|
|
|
247 |
return interceptorID;
|
|
|
248 |
}
|
|
|
249 |
|
|
|
250 |
+ (void)unregisterAppDelegateInterceptorWithID:(GULAppDelegateInterceptorID)interceptorID {
|
|
|
251 |
NSAssert(interceptorID, @"AppDelegateProxy cannot unregister nil interceptor ID.");
|
|
|
252 |
NSAssert(((NSString *)interceptorID).length != 0,
|
|
|
253 |
@"AppDelegateProxy cannot unregister empty interceptor ID.");
|
|
|
254 |
|
|
|
255 |
if (!interceptorID) {
|
|
|
256 |
GULLogError(kGULLoggerSwizzler, NO,
|
|
|
257 |
[NSString stringWithFormat:@"I-SWZ%06ld",
|
|
|
258 |
(long)kGULSwizzlerMessageCodeAppDelegateSwizzling003],
|
|
|
259 |
@"AppDelegateProxy cannot unregister empty interceptor ID.");
|
|
|
260 |
return;
|
|
|
261 |
}
|
|
|
262 |
|
|
|
263 |
GULZeroingWeakContainer *weakContainer = [GULAppDelegateSwizzler interceptors][interceptorID];
|
|
|
264 |
if (!weakContainer.object) {
|
|
|
265 |
GULLogError(kGULLoggerSwizzler, NO,
|
|
|
266 |
[NSString stringWithFormat:@"I-SWZ%06ld",
|
|
|
267 |
(long)kGULSwizzlerMessageCodeAppDelegateSwizzling004],
|
|
|
268 |
@"AppDelegateProxy cannot unregister interceptor that was not registered. "
|
|
|
269 |
"Interceptor ID %@",
|
|
|
270 |
interceptorID);
|
|
|
271 |
return;
|
|
|
272 |
}
|
|
|
273 |
|
|
|
274 |
[[GULAppDelegateSwizzler interceptors] removeObjectForKey:interceptorID];
|
|
|
275 |
}
|
|
|
276 |
|
|
|
277 |
+ (void)proxyOriginalDelegate {
|
|
|
278 |
if ([GULAppEnvironmentUtil isAppExtension]) {
|
|
|
279 |
return;
|
|
|
280 |
}
|
|
|
281 |
|
|
|
282 |
dispatch_once(&sProxyAppDelegateOnceToken, ^{
|
|
|
283 |
id<GULApplicationDelegate> originalDelegate =
|
|
|
284 |
[GULAppDelegateSwizzler sharedApplication].delegate;
|
|
|
285 |
[GULAppDelegateSwizzler proxyAppDelegate:originalDelegate];
|
|
|
286 |
});
|
|
|
287 |
}
|
|
|
288 |
|
|
|
289 |
+ (void)proxyOriginalDelegateIncludingAPNSMethods {
|
|
|
290 |
if ([GULAppEnvironmentUtil isAppExtension]) {
|
|
|
291 |
return;
|
|
|
292 |
}
|
|
|
293 |
|
|
|
294 |
[self proxyOriginalDelegate];
|
|
|
295 |
|
|
|
296 |
dispatch_once(&sProxyAppDelegateRemoteNotificationOnceToken, ^{
|
|
|
297 |
id<GULApplicationDelegate> appDelegate = [GULAppDelegateSwizzler sharedApplication].delegate;
|
|
|
298 |
|
|
|
299 |
NSMutableDictionary *realImplementationsBySelector =
|
|
|
300 |
[objc_getAssociatedObject(appDelegate, &kGULRealIMPBySelectorKey) mutableCopy];
|
|
|
301 |
|
|
|
302 |
[self proxyRemoteNotificationsMethodsWithAppDelegateSubClass:gAppDelegateSubclass
|
|
|
303 |
realClass:gOriginalAppDelegateClass
|
|
|
304 |
appDelegate:appDelegate
|
|
|
305 |
realImplementationsBySelector:realImplementationsBySelector];
|
|
|
306 |
|
|
|
307 |
objc_setAssociatedObject(appDelegate, &kGULRealIMPBySelectorKey,
|
|
|
308 |
[realImplementationsBySelector copy], OBJC_ASSOCIATION_RETAIN);
|
|
|
309 |
[self reassignAppDelegate];
|
|
|
310 |
});
|
|
|
311 |
}
|
|
|
312 |
|
|
|
313 |
#pragma mark - Create proxy
|
|
|
314 |
|
|
|
315 |
+ (GULApplication *)sharedApplication {
|
|
|
316 |
if ([GULAppEnvironmentUtil isAppExtension]) {
|
|
|
317 |
return nil;
|
|
|
318 |
}
|
|
|
319 |
id sharedApplication = nil;
|
|
|
320 |
Class uiApplicationClass = NSClassFromString(kGULApplicationClassName);
|
|
|
321 |
if (uiApplicationClass &&
|
|
|
322 |
[uiApplicationClass respondsToSelector:(NSSelectorFromString(@"sharedApplication"))]) {
|
|
|
323 |
sharedApplication = [uiApplicationClass sharedApplication];
|
|
|
324 |
}
|
|
|
325 |
return sharedApplication;
|
|
|
326 |
}
|
|
|
327 |
|
|
|
328 |
#pragma mark - Override default methods
|
|
|
329 |
|
|
|
330 |
/** Creates a new subclass of the class of the given object and sets the isa value of the given
|
|
|
331 |
* object to the new subclass. Additionally this copies methods to that new subclass that allow us
|
|
|
332 |
* to intercept UIApplicationDelegate methods. This is better known as isa swizzling.
|
|
|
333 |
*
|
|
|
334 |
* @param appDelegate The object to which you want to isa swizzle. This has to conform to the
|
|
|
335 |
* UIApplicationDelegate subclass.
|
|
|
336 |
* @return Returns the new subclass.
|
|
|
337 |
*/
|
|
|
338 |
+ (nullable Class)createSubclassWithObject:(id<GULApplicationDelegate>)appDelegate {
|
|
|
339 |
Class realClass = [appDelegate class];
|
|
|
340 |
|
|
|
341 |
// Create GUL_<RealAppDelegate>_<UUID>
|
|
|
342 |
NSString *classNameWithPrefix =
|
|
|
343 |
[kGULAppDelegatePrefix stringByAppendingString:NSStringFromClass(realClass)];
|
|
|
344 |
NSString *newClassName =
|
|
|
345 |
[NSString stringWithFormat:@"%@-%@", classNameWithPrefix, [NSUUID UUID].UUIDString];
|
|
|
346 |
|
|
|
347 |
if (NSClassFromString(newClassName)) {
|
|
|
348 |
GULLogError(kGULLoggerSwizzler, NO,
|
|
|
349 |
[NSString stringWithFormat:@"I-SWZ%06ld",
|
|
|
350 |
(long)kGULSwizzlerMessageCodeAppDelegateSwizzling005],
|
|
|
351 |
@"Cannot create a proxy for App Delegate. Subclass already exists. Original Class: "
|
|
|
352 |
@"%@, subclass: %@",
|
|
|
353 |
NSStringFromClass(realClass), newClassName);
|
|
|
354 |
return nil;
|
|
|
355 |
}
|
|
|
356 |
|
|
|
357 |
// Register the new class as subclass of the real one. Do not allocate more than the real class
|
|
|
358 |
// size.
|
|
|
359 |
Class appDelegateSubClass = objc_allocateClassPair(realClass, newClassName.UTF8String, 0);
|
|
|
360 |
if (appDelegateSubClass == Nil) {
|
|
|
361 |
GULLogError(kGULLoggerSwizzler, NO,
|
|
|
362 |
[NSString stringWithFormat:@"I-SWZ%06ld",
|
|
|
363 |
(long)kGULSwizzlerMessageCodeAppDelegateSwizzling006],
|
|
|
364 |
@"Cannot create a proxy for App Delegate. Subclass already exists. Original Class: "
|
|
|
365 |
@"%@, subclass: Nil",
|
|
|
366 |
NSStringFromClass(realClass));
|
|
|
367 |
return nil;
|
|
|
368 |
}
|
|
|
369 |
|
|
|
370 |
NSMutableDictionary<NSString *, NSValue *> *realImplementationsBySelector =
|
|
|
371 |
[[NSMutableDictionary alloc] init];
|
|
|
372 |
|
|
|
373 |
// For application:continueUserActivity:restorationHandler:
|
|
|
374 |
SEL continueUserActivitySEL = @selector(application:continueUserActivity:restorationHandler:);
|
|
|
375 |
[self proxyDestinationSelector:continueUserActivitySEL
|
|
|
376 |
implementationsFromSourceSelector:continueUserActivitySEL
|
|
|
377 |
fromClass:[GULAppDelegateSwizzler class]
|
|
|
378 |
toClass:appDelegateSubClass
|
|
|
379 |
realClass:realClass
|
|
|
380 |
storeDestinationImplementationTo:realImplementationsBySelector];
|
|
|
381 |
|
|
|
382 |
#if TARGET_OS_IOS || TARGET_OS_TV
|
|
|
383 |
// Add the following methods from GULAppDelegate class, and store the real implementation so it
|
|
|
384 |
// can forward to the real one.
|
|
|
385 |
// For application:openURL:options:
|
|
|
386 |
SEL applicationOpenURLOptionsSEL = @selector(application:openURL:options:);
|
|
|
387 |
if ([appDelegate respondsToSelector:applicationOpenURLOptionsSEL]) {
|
|
|
388 |
// Only add the application:openURL:options: method if the original AppDelegate implements it.
|
|
|
389 |
// This fixes a bug if an app only implements application:openURL:sourceApplication:annotation:
|
|
|
390 |
// (if we add the `options` method, iOS sees that one exists and does not call the
|
|
|
391 |
// `sourceApplication` method, which in this case is the only one the app implements).
|
|
|
392 |
|
|
|
393 |
[self proxyDestinationSelector:applicationOpenURLOptionsSEL
|
|
|
394 |
implementationsFromSourceSelector:applicationOpenURLOptionsSEL
|
|
|
395 |
fromClass:[GULAppDelegateSwizzler class]
|
|
|
396 |
toClass:appDelegateSubClass
|
|
|
397 |
realClass:realClass
|
|
|
398 |
storeDestinationImplementationTo:realImplementationsBySelector];
|
|
|
399 |
}
|
|
|
400 |
|
|
|
401 |
// For application:handleEventsForBackgroundURLSession:completionHandler:
|
|
|
402 |
SEL handleEventsForBackgroundURLSessionSEL = @selector(application:
|
|
|
403 |
handleEventsForBackgroundURLSession:completionHandler:);
|
|
|
404 |
[self proxyDestinationSelector:handleEventsForBackgroundURLSessionSEL
|
|
|
405 |
implementationsFromSourceSelector:handleEventsForBackgroundURLSessionSEL
|
|
|
406 |
fromClass:[GULAppDelegateSwizzler class]
|
|
|
407 |
toClass:appDelegateSubClass
|
|
|
408 |
realClass:realClass
|
|
|
409 |
storeDestinationImplementationTo:realImplementationsBySelector];
|
|
|
410 |
#endif // TARGET_OS_IOS || TARGET_OS_TV
|
|
|
411 |
|
|
|
412 |
#if TARGET_OS_IOS
|
|
|
413 |
// For application:openURL:sourceApplication:annotation:
|
|
|
414 |
SEL openURLSourceApplicationAnnotationSEL = @selector(application:
|
|
|
415 |
openURL:sourceApplication:annotation:);
|
|
|
416 |
|
|
|
417 |
[self proxyDestinationSelector:openURLSourceApplicationAnnotationSEL
|
|
|
418 |
implementationsFromSourceSelector:openURLSourceApplicationAnnotationSEL
|
|
|
419 |
fromClass:[GULAppDelegateSwizzler class]
|
|
|
420 |
toClass:appDelegateSubClass
|
|
|
421 |
realClass:realClass
|
|
|
422 |
storeDestinationImplementationTo:realImplementationsBySelector];
|
|
|
423 |
#endif // TARGET_OS_IOS
|
|
|
424 |
|
|
|
425 |
// Override the description too so the custom class name will not show up.
|
|
|
426 |
[GULAppDelegateSwizzler addInstanceMethodWithDestinationSelector:@selector(description)
|
|
|
427 |
withImplementationFromSourceSelector:@selector(fakeDescription)
|
|
|
428 |
fromClass:[self class]
|
|
|
429 |
toClass:appDelegateSubClass];
|
|
|
430 |
|
|
|
431 |
// Store original implementations to a fake property of the original delegate.
|
|
|
432 |
objc_setAssociatedObject(appDelegate, &kGULRealIMPBySelectorKey,
|
|
|
433 |
[realImplementationsBySelector copy], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
|
|
434 |
objc_setAssociatedObject(appDelegate, &kGULRealClassKey, realClass,
|
|
|
435 |
OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
|
|
436 |
|
|
|
437 |
// The subclass size has to be exactly the same size with the original class size. The subclass
|
|
|
438 |
// cannot have more ivars/properties than its superclass since it will cause an offset in memory
|
|
|
439 |
// that can lead to overwriting the isa of an object in the next frame.
|
|
|
440 |
if (class_getInstanceSize(realClass) != class_getInstanceSize(appDelegateSubClass)) {
|
|
|
441 |
GULLogError(kGULLoggerSwizzler, NO,
|
|
|
442 |
[NSString stringWithFormat:@"I-SWZ%06ld",
|
|
|
443 |
(long)kGULSwizzlerMessageCodeAppDelegateSwizzling007],
|
|
|
444 |
@"Cannot create subclass of App Delegate, because the created subclass is not the "
|
|
|
445 |
@"same size. %@",
|
|
|
446 |
NSStringFromClass(realClass));
|
|
|
447 |
NSAssert(NO, @"Classes must be the same size to swizzle isa");
|
|
|
448 |
return nil;
|
|
|
449 |
}
|
|
|
450 |
|
|
|
451 |
// Make the newly created class to be the subclass of the real App Delegate class.
|
|
|
452 |
objc_registerClassPair(appDelegateSubClass);
|
|
|
453 |
if (object_setClass(appDelegate, appDelegateSubClass)) {
|
|
|
454 |
GULLogDebug(kGULLoggerSwizzler, NO,
|
|
|
455 |
[NSString stringWithFormat:@"I-SWZ%06ld",
|
|
|
456 |
(long)kGULSwizzlerMessageCodeAppDelegateSwizzling008],
|
|
|
457 |
@"Successfully created App Delegate Proxy automatically. To disable the "
|
|
|
458 |
@"proxy, set the flag %@ to NO (Boolean) in the Info.plist",
|
|
|
459 |
[GULAppDelegateSwizzler correctAppDelegateProxyKey]);
|
|
|
460 |
}
|
|
|
461 |
|
|
|
462 |
return appDelegateSubClass;
|
|
|
463 |
}
|
|
|
464 |
|
|
|
465 |
+ (void)proxyRemoteNotificationsMethodsWithAppDelegateSubClass:(Class)appDelegateSubClass
|
|
|
466 |
realClass:(Class)realClass
|
|
|
467 |
appDelegate:(id)appDelegate
|
|
|
468 |
realImplementationsBySelector:
|
|
|
469 |
(NSMutableDictionary *)realImplementationsBySelector {
|
|
|
470 |
if (realClass == nil || appDelegateSubClass == nil || appDelegate == nil ||
|
|
|
471 |
realImplementationsBySelector == nil) {
|
|
|
472 |
// The App Delegate has not been swizzled.
|
|
|
473 |
return;
|
|
|
474 |
}
|
|
|
475 |
|
|
|
476 |
// For application:didRegisterForRemoteNotificationsWithDeviceToken:
|
|
|
477 |
SEL didRegisterForRemoteNotificationsSEL =
|
|
|
478 |
NSSelectorFromString(kGULDidRegisterForRemoteNotificationsSEL);
|
|
|
479 |
SEL didRegisterForRemoteNotificationsDonorSEL = @selector(application:
|
|
|
480 |
donor_didRegisterForRemoteNotificationsWithDeviceToken:);
|
|
|
481 |
|
|
|
482 |
[self proxyDestinationSelector:didRegisterForRemoteNotificationsSEL
|
|
|
483 |
implementationsFromSourceSelector:didRegisterForRemoteNotificationsDonorSEL
|
|
|
484 |
fromClass:[GULAppDelegateSwizzler class]
|
|
|
485 |
toClass:appDelegateSubClass
|
|
|
486 |
realClass:realClass
|
|
|
487 |
storeDestinationImplementationTo:realImplementationsBySelector];
|
|
|
488 |
|
|
|
489 |
// For application:didFailToRegisterForRemoteNotificationsWithError:
|
|
|
490 |
SEL didFailToRegisterForRemoteNotificationsSEL =
|
|
|
491 |
NSSelectorFromString(kGULDidFailToRegisterForRemoteNotificationsSEL);
|
|
|
492 |
SEL didFailToRegisterForRemoteNotificationsDonorSEL = @selector(application:
|
|
|
493 |
donor_didFailToRegisterForRemoteNotificationsWithError:);
|
|
|
494 |
|
|
|
495 |
[self proxyDestinationSelector:didFailToRegisterForRemoteNotificationsSEL
|
|
|
496 |
implementationsFromSourceSelector:didFailToRegisterForRemoteNotificationsDonorSEL
|
|
|
497 |
fromClass:[GULAppDelegateSwizzler class]
|
|
|
498 |
toClass:appDelegateSubClass
|
|
|
499 |
realClass:realClass
|
|
|
500 |
storeDestinationImplementationTo:realImplementationsBySelector];
|
|
|
501 |
|
|
|
502 |
// For application:didReceiveRemoteNotification:
|
|
|
503 |
SEL didReceiveRemoteNotificationSEL = NSSelectorFromString(kGULDidReceiveRemoteNotificationSEL);
|
|
|
504 |
SEL didReceiveRemoteNotificationDonotSEL = @selector(application:
|
|
|
505 |
donor_didReceiveRemoteNotification:);
|
|
|
506 |
|
|
|
507 |
[self proxyDestinationSelector:didReceiveRemoteNotificationSEL
|
|
|
508 |
implementationsFromSourceSelector:didReceiveRemoteNotificationDonotSEL
|
|
|
509 |
fromClass:[GULAppDelegateSwizzler class]
|
|
|
510 |
toClass:appDelegateSubClass
|
|
|
511 |
realClass:realClass
|
|
|
512 |
storeDestinationImplementationTo:realImplementationsBySelector];
|
|
|
513 |
|
|
|
514 |
// For application:didReceiveRemoteNotification:fetchCompletionHandler:
|
|
|
515 |
#if !TARGET_OS_WATCH && !TARGET_OS_OSX
|
|
|
516 |
SEL didReceiveRemoteNotificationWithCompletionSEL =
|
|
|
517 |
NSSelectorFromString(kGULDidReceiveRemoteNotificationWithCompletionSEL);
|
|
|
518 |
SEL didReceiveRemoteNotificationWithCompletionDonorSEL =
|
|
|
519 |
@selector(application:donor_didReceiveRemoteNotification:fetchCompletionHandler:);
|
|
|
520 |
if ([appDelegate respondsToSelector:didReceiveRemoteNotificationWithCompletionSEL]) {
|
|
|
521 |
// Only add the application:didReceiveRemoteNotification:fetchCompletionHandler: method if
|
|
|
522 |
// the original AppDelegate implements it.
|
|
|
523 |
// This fixes a bug if an app only implements application:didReceiveRemoteNotification:
|
|
|
524 |
// (if we add the method with completion, iOS sees that one exists and does not call
|
|
|
525 |
// the method without the completion, which in this case is the only one the app implements).
|
|
|
526 |
|
|
|
527 |
[self proxyDestinationSelector:didReceiveRemoteNotificationWithCompletionSEL
|
|
|
528 |
implementationsFromSourceSelector:didReceiveRemoteNotificationWithCompletionDonorSEL
|
|
|
529 |
fromClass:[GULAppDelegateSwizzler class]
|
|
|
530 |
toClass:appDelegateSubClass
|
|
|
531 |
realClass:realClass
|
|
|
532 |
storeDestinationImplementationTo:realImplementationsBySelector];
|
|
|
533 |
}
|
|
|
534 |
#endif // !TARGET_OS_WATCH && !TARGET_OS_OSX
|
|
|
535 |
}
|
|
|
536 |
|
|
|
537 |
/// We have to do this to invalidate the cache that caches the original respondsToSelector of
|
|
|
538 |
/// openURL handlers. Without this, it won't call the default implementations because the system
|
|
|
539 |
/// checks and caches them.
|
|
|
540 |
/// Register KVO only once. Otherwise, the observing method will be called as many times as
|
|
|
541 |
/// being registered.
|
|
|
542 |
+ (void)reassignAppDelegate {
|
|
|
543 |
#if !TARGET_OS_WATCH
|
|
|
544 |
id<GULApplicationDelegate> delegate = [self sharedApplication].delegate;
|
|
|
545 |
[self sharedApplication].delegate = nil;
|
|
|
546 |
[self sharedApplication].delegate = delegate;
|
|
|
547 |
gOriginalAppDelegate = delegate;
|
|
|
548 |
[[GULAppDelegateObserver sharedInstance] observeUIApplication];
|
|
|
549 |
#endif
|
|
|
550 |
}
|
|
|
551 |
|
|
|
552 |
#pragma mark - Helper methods
|
|
|
553 |
|
|
|
554 |
+ (GULMutableDictionary *)interceptors {
|
|
|
555 |
static dispatch_once_t onceToken;
|
|
|
556 |
static GULMutableDictionary *sInterceptors;
|
|
|
557 |
dispatch_once(&onceToken, ^{
|
|
|
558 |
sInterceptors = [[GULMutableDictionary alloc] init];
|
|
|
559 |
});
|
|
|
560 |
return sInterceptors;
|
|
|
561 |
}
|
|
|
562 |
|
|
|
563 |
+ (nullable NSValue *)originalImplementationForSelector:(SEL)selector object:(id)object {
|
|
|
564 |
NSDictionary *realImplementationBySelector =
|
|
|
565 |
objc_getAssociatedObject(object, &kGULRealIMPBySelectorKey);
|
|
|
566 |
return realImplementationBySelector[NSStringFromSelector(selector)];
|
|
|
567 |
}
|
|
|
568 |
|
|
|
569 |
+ (void)proxyDestinationSelector:(SEL)destinationSelector
|
|
|
570 |
implementationsFromSourceSelector:(SEL)sourceSelector
|
|
|
571 |
fromClass:(Class)sourceClass
|
|
|
572 |
toClass:(Class)destinationClass
|
|
|
573 |
realClass:(Class)realClass
|
|
|
574 |
storeDestinationImplementationTo:
|
|
|
575 |
(NSMutableDictionary<NSString *, NSValue *> *)destinationImplementationsBySelector {
|
|
|
576 |
[self addInstanceMethodWithDestinationSelector:destinationSelector
|
|
|
577 |
withImplementationFromSourceSelector:sourceSelector
|
|
|
578 |
fromClass:sourceClass
|
|
|
579 |
toClass:destinationClass];
|
|
|
580 |
IMP sourceImplementation =
|
|
|
581 |
[GULAppDelegateSwizzler implementationOfMethodSelector:destinationSelector
|
|
|
582 |
fromClass:realClass];
|
|
|
583 |
NSValue *sourceImplementationPointer = [NSValue valueWithPointer:sourceImplementation];
|
|
|
584 |
|
|
|
585 |
NSString *destinationSelectorString = NSStringFromSelector(destinationSelector);
|
|
|
586 |
destinationImplementationsBySelector[destinationSelectorString] = sourceImplementationPointer;
|
|
|
587 |
}
|
|
|
588 |
|
|
|
589 |
/** Copies a method identified by the methodSelector from one class to the other. After this method
|
|
|
590 |
* is called, performing [toClassInstance methodSelector] will be similar to calling
|
|
|
591 |
* [fromClassInstance methodSelector]. This method does nothing if toClass already has a method
|
|
|
592 |
* identified by methodSelector.
|
|
|
593 |
*
|
|
|
594 |
* @param methodSelector The SEL that identifies both the method on the fromClass as well as the
|
|
|
595 |
* one on the toClass.
|
|
|
596 |
* @param fromClass The class from which a method is sourced.
|
|
|
597 |
* @param toClass The class to which the method is added. If the class already has a method with
|
|
|
598 |
* the same selector, this has no effect.
|
|
|
599 |
*/
|
|
|
600 |
+ (void)addInstanceMethodWithSelector:(SEL)methodSelector
|
|
|
601 |
fromClass:(Class)fromClass
|
|
|
602 |
toClass:(Class)toClass {
|
|
|
603 |
[self addInstanceMethodWithDestinationSelector:methodSelector
|
|
|
604 |
withImplementationFromSourceSelector:methodSelector
|
|
|
605 |
fromClass:fromClass
|
|
|
606 |
toClass:toClass];
|
|
|
607 |
}
|
|
|
608 |
|
|
|
609 |
/** Copies a method identified by the sourceSelector from the fromClass as a method for the
|
|
|
610 |
* destinationSelector on the toClass. After this method is called, performing
|
|
|
611 |
* [toClassInstance destinationSelector] will be similar to calling
|
|
|
612 |
* [fromClassInstance sourceSelector]. This method does nothing if toClass already has a method
|
|
|
613 |
* identified by destinationSelector.
|
|
|
614 |
*
|
|
|
615 |
* @param destinationSelector The SEL that identifies the method on the toClass.
|
|
|
616 |
* @param sourceSelector The SEL that identifies the method on the fromClass.
|
|
|
617 |
* @param fromClass The class from which a method is sourced.
|
|
|
618 |
* @param toClass The class to which the method is added. If the class already has a method with
|
|
|
619 |
* the same selector, this has no effect.
|
|
|
620 |
*/
|
|
|
621 |
+ (void)addInstanceMethodWithDestinationSelector:(SEL)destinationSelector
|
|
|
622 |
withImplementationFromSourceSelector:(SEL)sourceSelector
|
|
|
623 |
fromClass:(Class)fromClass
|
|
|
624 |
toClass:(Class)toClass {
|
|
|
625 |
Method method = class_getInstanceMethod(fromClass, sourceSelector);
|
|
|
626 |
IMP methodIMP = method_getImplementation(method);
|
|
|
627 |
const char *types = method_getTypeEncoding(method);
|
|
|
628 |
if (!class_addMethod(toClass, destinationSelector, methodIMP, types)) {
|
|
|
629 |
GULLogWarning(kGULLoggerSwizzler, NO,
|
|
|
630 |
[NSString stringWithFormat:@"I-SWZ%06ld",
|
|
|
631 |
(long)kGULSwizzlerMessageCodeAppDelegateSwizzling009],
|
|
|
632 |
@"Cannot copy method to destination selector %@ as it already exists",
|
|
|
633 |
NSStringFromSelector(destinationSelector));
|
|
|
634 |
}
|
|
|
635 |
}
|
|
|
636 |
|
|
|
637 |
/** Gets the IMP of the instance method on the class identified by the selector.
|
|
|
638 |
*
|
|
|
639 |
* @param selector The selector of which the IMP is to be fetched.
|
|
|
640 |
* @param aClass The class from which the IMP is to be fetched.
|
|
|
641 |
* @return The IMP of the instance method identified by selector and aClass.
|
|
|
642 |
*/
|
|
|
643 |
+ (IMP)implementationOfMethodSelector:(SEL)selector fromClass:(Class)aClass {
|
|
|
644 |
Method aMethod = class_getInstanceMethod(aClass, selector);
|
|
|
645 |
return method_getImplementation(aMethod);
|
|
|
646 |
}
|
|
|
647 |
|
|
|
648 |
/** Enumerates through all the interceptors and if they respond to a given selector, executes a
|
|
|
649 |
* GULAppDelegateInterceptorCallback with the interceptor.
|
|
|
650 |
*
|
|
|
651 |
* @param methodSelector The SEL to check if an interceptor responds to.
|
|
|
652 |
* @param callback the GULAppDelegateInterceptorCallback.
|
|
|
653 |
*/
|
|
|
654 |
+ (void)notifyInterceptorsWithMethodSelector:(SEL)methodSelector
|
|
|
655 |
callback:(GULAppDelegateInterceptorCallback)callback {
|
|
|
656 |
if (!callback) {
|
|
|
657 |
return;
|
|
|
658 |
}
|
|
|
659 |
|
|
|
660 |
NSDictionary *interceptors = [GULAppDelegateSwizzler interceptors].dictionary;
|
|
|
661 |
[interceptors enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
|
|
|
662 |
GULZeroingWeakContainer *interceptorContainer = obj;
|
|
|
663 |
id interceptor = interceptorContainer.object;
|
|
|
664 |
if (!interceptor) {
|
|
|
665 |
GULLogWarning(
|
|
|
666 |
kGULLoggerSwizzler, NO,
|
|
|
667 |
[NSString
|
|
|
668 |
stringWithFormat:@"I-SWZ%06ld", (long)kGULSwizzlerMessageCodeAppDelegateSwizzling010],
|
|
|
669 |
@"AppDelegateProxy cannot find interceptor with ID %@. Removing the interceptor.", key);
|
|
|
670 |
[[GULAppDelegateSwizzler interceptors] removeObjectForKey:key];
|
|
|
671 |
return;
|
|
|
672 |
}
|
|
|
673 |
if ([interceptor respondsToSelector:methodSelector]) {
|
|
|
674 |
callback(interceptor);
|
|
|
675 |
}
|
|
|
676 |
}];
|
|
|
677 |
}
|
|
|
678 |
|
|
|
679 |
// The methods below are donor methods which are added to the dynamic subclass of the App Delegate.
|
|
|
680 |
// They are called within the scope of the real App Delegate so |self| does not refer to the
|
|
|
681 |
// GULAppDelegateSwizzler instance but the real App Delegate instance.
|
|
|
682 |
|
|
|
683 |
#pragma mark - [Donor Methods] Overridden instance description method
|
|
|
684 |
|
|
|
685 |
- (NSString *)fakeDescription {
|
|
|
686 |
Class realClass = objc_getAssociatedObject(self, &kGULRealClassKey);
|
|
|
687 |
return [NSString stringWithFormat:@"<%@: %p>", realClass, self];
|
|
|
688 |
}
|
|
|
689 |
|
|
|
690 |
#pragma mark - [Donor Methods] URL overridden handler methods
|
|
|
691 |
#if TARGET_OS_IOS || TARGET_OS_TV
|
|
|
692 |
|
|
|
693 |
- (BOOL)application:(GULApplication *)application
|
|
|
694 |
openURL:(NSURL *)url
|
|
|
695 |
options:(NSDictionary<NSString *, id> *)options {
|
|
|
696 |
SEL methodSelector = @selector(application:openURL:options:);
|
|
|
697 |
// Call the real implementation if the real App Delegate has any.
|
|
|
698 |
NSValue *openURLIMPPointer =
|
|
|
699 |
[GULAppDelegateSwizzler originalImplementationForSelector:methodSelector object:self];
|
|
|
700 |
GULRealOpenURLOptionsIMP openURLOptionsIMP = [openURLIMPPointer pointerValue];
|
|
|
701 |
|
|
|
702 |
__block BOOL returnedValue = NO;
|
|
|
703 |
|
|
|
704 |
// This is needed to for the library to be warning free on iOS versions < 9.
|
|
|
705 |
#pragma clang diagnostic push
|
|
|
706 |
#pragma clang diagnostic ignored "-Wunguarded-availability"
|
|
|
707 |
[GULAppDelegateSwizzler
|
|
|
708 |
notifyInterceptorsWithMethodSelector:methodSelector
|
|
|
709 |
callback:^(id<GULApplicationDelegate> interceptor) {
|
|
|
710 |
returnedValue |= [interceptor application:application
|
|
|
711 |
openURL:url
|
|
|
712 |
options:options];
|
|
|
713 |
}];
|
|
|
714 |
#pragma clang diagnostic pop
|
|
|
715 |
if (openURLOptionsIMP) {
|
|
|
716 |
returnedValue |= openURLOptionsIMP(self, methodSelector, application, url, options);
|
|
|
717 |
}
|
|
|
718 |
return returnedValue;
|
|
|
719 |
}
|
|
|
720 |
|
|
|
721 |
#endif // TARGET_OS_IOS || TARGET_OS_TV
|
|
|
722 |
|
|
|
723 |
#if TARGET_OS_IOS
|
|
|
724 |
|
|
|
725 |
- (BOOL)application:(GULApplication *)application
|
|
|
726 |
openURL:(NSURL *)url
|
|
|
727 |
sourceApplication:(NSString *)sourceApplication
|
|
|
728 |
annotation:(id)annotation {
|
|
|
729 |
SEL methodSelector = @selector(application:openURL:sourceApplication:annotation:);
|
|
|
730 |
|
|
|
731 |
// Call the real implementation if the real App Delegate has any.
|
|
|
732 |
NSValue *openURLSourceAppAnnotationIMPPointer =
|
|
|
733 |
[GULAppDelegateSwizzler originalImplementationForSelector:methodSelector object:self];
|
|
|
734 |
GULRealOpenURLSourceApplicationAnnotationIMP openURLSourceApplicationAnnotationIMP =
|
|
|
735 |
[openURLSourceAppAnnotationIMPPointer pointerValue];
|
|
|
736 |
|
|
|
737 |
__block BOOL returnedValue = NO;
|
|
|
738 |
[GULAppDelegateSwizzler
|
|
|
739 |
notifyInterceptorsWithMethodSelector:methodSelector
|
|
|
740 |
callback:^(id<GULApplicationDelegate> interceptor) {
|
|
|
741 |
#pragma clang diagnostic push
|
|
|
742 |
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
|
|
743 |
returnedValue |= [interceptor application:application
|
|
|
744 |
openURL:url
|
|
|
745 |
sourceApplication:sourceApplication
|
|
|
746 |
annotation:annotation];
|
|
|
747 |
#pragma clang diagnostic pop
|
|
|
748 |
}];
|
|
|
749 |
if (openURLSourceApplicationAnnotationIMP) {
|
|
|
750 |
returnedValue |= openURLSourceApplicationAnnotationIMP(self, methodSelector, application, url,
|
|
|
751 |
sourceApplication, annotation);
|
|
|
752 |
}
|
|
|
753 |
return returnedValue;
|
|
|
754 |
}
|
|
|
755 |
|
|
|
756 |
#endif // TARGET_OS_IOS
|
|
|
757 |
|
|
|
758 |
#pragma mark - [Donor Methods] Network overridden handler methods
|
|
|
759 |
|
|
|
760 |
#if TARGET_OS_IOS || TARGET_OS_TV
|
|
|
761 |
|
|
|
762 |
#pragma clang diagnostic push
|
|
|
763 |
#pragma clang diagnostic ignored "-Wstrict-prototypes"
|
|
|
764 |
- (void)application:(GULApplication *)application
|
|
|
765 |
handleEventsForBackgroundURLSession:(NSString *)identifier
|
|
|
766 |
completionHandler:(void (^)())completionHandler API_AVAILABLE(ios(7.0)) {
|
|
|
767 |
#pragma clang diagnostic pop
|
|
|
768 |
SEL methodSelector = @selector(application:
|
|
|
769 |
handleEventsForBackgroundURLSession:completionHandler:);
|
|
|
770 |
NSValue *handleBackgroundSessionPointer =
|
|
|
771 |
[GULAppDelegateSwizzler originalImplementationForSelector:methodSelector object:self];
|
|
|
772 |
GULRealHandleEventsForBackgroundURLSessionIMP handleBackgroundSessionIMP =
|
|
|
773 |
[handleBackgroundSessionPointer pointerValue];
|
|
|
774 |
|
|
|
775 |
// Notify interceptors.
|
|
|
776 |
[GULAppDelegateSwizzler
|
|
|
777 |
notifyInterceptorsWithMethodSelector:methodSelector
|
|
|
778 |
callback:^(id<GULApplicationDelegate> interceptor) {
|
|
|
779 |
[interceptor application:application
|
|
|
780 |
handleEventsForBackgroundURLSession:identifier
|
|
|
781 |
completionHandler:completionHandler];
|
|
|
782 |
}];
|
|
|
783 |
// Call the real implementation if the real App Delegate has any.
|
|
|
784 |
if (handleBackgroundSessionIMP) {
|
|
|
785 |
handleBackgroundSessionIMP(self, methodSelector, application, identifier, completionHandler);
|
|
|
786 |
}
|
|
|
787 |
}
|
|
|
788 |
|
|
|
789 |
#endif // TARGET_OS_IOS || TARGET_OS_TV
|
|
|
790 |
|
|
|
791 |
#pragma mark - [Donor Methods] User Activities overridden handler methods
|
|
|
792 |
|
|
|
793 |
- (BOOL)application:(GULApplication *)application
|
|
|
794 |
continueUserActivity:(NSUserActivity *)userActivity
|
|
|
795 |
restorationHandler:(void (^)(NSArray *restorableObjects))restorationHandler {
|
|
|
796 |
SEL methodSelector = @selector(application:continueUserActivity:restorationHandler:);
|
|
|
797 |
NSValue *continueUserActivityIMPPointer =
|
|
|
798 |
[GULAppDelegateSwizzler originalImplementationForSelector:methodSelector object:self];
|
|
|
799 |
GULRealContinueUserActivityIMP continueUserActivityIMP =
|
|
|
800 |
continueUserActivityIMPPointer.pointerValue;
|
|
|
801 |
|
|
|
802 |
__block BOOL returnedValue = NO;
|
|
|
803 |
#if !TARGET_OS_WATCH
|
|
|
804 |
[GULAppDelegateSwizzler
|
|
|
805 |
notifyInterceptorsWithMethodSelector:methodSelector
|
|
|
806 |
callback:^(id<GULApplicationDelegate> interceptor) {
|
|
|
807 |
returnedValue |= [interceptor application:application
|
|
|
808 |
continueUserActivity:userActivity
|
|
|
809 |
restorationHandler:restorationHandler];
|
|
|
810 |
}];
|
|
|
811 |
#endif
|
|
|
812 |
// Call the real implementation if the real App Delegate has any.
|
|
|
813 |
if (continueUserActivityIMP) {
|
|
|
814 |
returnedValue |= continueUserActivityIMP(self, methodSelector, application, userActivity,
|
|
|
815 |
restorationHandler);
|
|
|
816 |
}
|
|
|
817 |
return returnedValue;
|
|
|
818 |
}
|
|
|
819 |
|
|
|
820 |
#pragma mark - [Donor Methods] Remote Notifications
|
|
|
821 |
|
|
|
822 |
- (void)application:(GULApplication *)application
|
|
|
823 |
donor_didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
|
|
|
824 |
SEL methodSelector = NSSelectorFromString(kGULDidRegisterForRemoteNotificationsSEL);
|
|
|
825 |
|
|
|
826 |
NSValue *didRegisterForRemoteNotificationsIMPPointer =
|
|
|
827 |
[GULAppDelegateSwizzler originalImplementationForSelector:methodSelector object:self];
|
|
|
828 |
GULRealDidRegisterForRemoteNotificationsIMP didRegisterForRemoteNotificationsIMP =
|
|
|
829 |
[didRegisterForRemoteNotificationsIMPPointer pointerValue];
|
|
|
830 |
|
|
|
831 |
// Notify interceptors.
|
|
|
832 |
[GULAppDelegateSwizzler
|
|
|
833 |
notifyInterceptorsWithMethodSelector:methodSelector
|
|
|
834 |
callback:^(id<GULApplicationDelegate> interceptor) {
|
|
|
835 |
NSInvocation *invocation = [GULAppDelegateSwizzler
|
|
|
836 |
appDelegateInvocationForSelector:methodSelector];
|
|
|
837 |
[invocation setTarget:interceptor];
|
|
|
838 |
[invocation setSelector:methodSelector];
|
|
|
839 |
[invocation setArgument:(void *)(&application) atIndex:2];
|
|
|
840 |
[invocation setArgument:(void *)(&deviceToken) atIndex:3];
|
|
|
841 |
[invocation invoke];
|
|
|
842 |
}];
|
|
|
843 |
// Call the real implementation if the real App Delegate has any.
|
|
|
844 |
if (didRegisterForRemoteNotificationsIMP) {
|
|
|
845 |
didRegisterForRemoteNotificationsIMP(self, methodSelector, application, deviceToken);
|
|
|
846 |
}
|
|
|
847 |
}
|
|
|
848 |
|
|
|
849 |
- (void)application:(GULApplication *)application
|
|
|
850 |
donor_didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
|
|
|
851 |
SEL methodSelector = NSSelectorFromString(kGULDidFailToRegisterForRemoteNotificationsSEL);
|
|
|
852 |
NSValue *didFailToRegisterForRemoteNotificationsIMPPointer =
|
|
|
853 |
[GULAppDelegateSwizzler originalImplementationForSelector:methodSelector object:self];
|
|
|
854 |
GULRealDidFailToRegisterForRemoteNotificationsIMP didFailToRegisterForRemoteNotificationsIMP =
|
|
|
855 |
[didFailToRegisterForRemoteNotificationsIMPPointer pointerValue];
|
|
|
856 |
|
|
|
857 |
// Notify interceptors.
|
|
|
858 |
[GULAppDelegateSwizzler
|
|
|
859 |
notifyInterceptorsWithMethodSelector:methodSelector
|
|
|
860 |
callback:^(id<GULApplicationDelegate> interceptor) {
|
|
|
861 |
NSInvocation *invocation = [GULAppDelegateSwizzler
|
|
|
862 |
appDelegateInvocationForSelector:methodSelector];
|
|
|
863 |
[invocation setTarget:interceptor];
|
|
|
864 |
[invocation setSelector:methodSelector];
|
|
|
865 |
[invocation setArgument:(void *)(&application) atIndex:2];
|
|
|
866 |
[invocation setArgument:(void *)(&error) atIndex:3];
|
|
|
867 |
[invocation invoke];
|
|
|
868 |
}];
|
|
|
869 |
// Call the real implementation if the real App Delegate has any.
|
|
|
870 |
if (didFailToRegisterForRemoteNotificationsIMP) {
|
|
|
871 |
didFailToRegisterForRemoteNotificationsIMP(self, methodSelector, application, error);
|
|
|
872 |
}
|
|
|
873 |
}
|
|
|
874 |
|
|
|
875 |
#if !TARGET_OS_WATCH && !TARGET_OS_OSX
|
|
|
876 |
- (void)application:(GULApplication *)application
|
|
|
877 |
donor_didReceiveRemoteNotification:(NSDictionary *)userInfo
|
|
|
878 |
fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
|
|
|
879 |
SEL methodSelector = NSSelectorFromString(kGULDidReceiveRemoteNotificationWithCompletionSEL);
|
|
|
880 |
NSValue *didReceiveRemoteNotificationWithCompletionIMPPointer =
|
|
|
881 |
[GULAppDelegateSwizzler originalImplementationForSelector:methodSelector object:self];
|
|
|
882 |
GULRealDidReceiveRemoteNotificationWithCompletionIMP
|
|
|
883 |
didReceiveRemoteNotificationWithCompletionIMP =
|
|
|
884 |
[didReceiveRemoteNotificationWithCompletionIMPPointer pointerValue];
|
|
|
885 |
|
|
|
886 |
dispatch_group_t __block callbackGroup = dispatch_group_create();
|
|
|
887 |
NSMutableArray<NSNumber *> *__block fetchResults = [NSMutableArray array];
|
|
|
888 |
|
|
|
889 |
void (^localCompletionHandler)(UIBackgroundFetchResult) =
|
|
|
890 |
^void(UIBackgroundFetchResult fetchResult) {
|
|
|
891 |
[fetchResults addObject:[NSNumber numberWithInt:(int)fetchResult]];
|
|
|
892 |
dispatch_group_leave(callbackGroup);
|
|
|
893 |
};
|
|
|
894 |
|
|
|
895 |
// Notify interceptors.
|
|
|
896 |
[GULAppDelegateSwizzler
|
|
|
897 |
notifyInterceptorsWithMethodSelector:methodSelector
|
|
|
898 |
callback:^(id<GULApplicationDelegate> interceptor) {
|
|
|
899 |
dispatch_group_enter(callbackGroup);
|
|
|
900 |
|
|
|
901 |
NSInvocation *invocation = [GULAppDelegateSwizzler
|
|
|
902 |
appDelegateInvocationForSelector:methodSelector];
|
|
|
903 |
[invocation setTarget:interceptor];
|
|
|
904 |
[invocation setSelector:methodSelector];
|
|
|
905 |
[invocation setArgument:(void *)(&application) atIndex:2];
|
|
|
906 |
[invocation setArgument:(void *)(&userInfo) atIndex:3];
|
|
|
907 |
[invocation setArgument:(void *)(&localCompletionHandler)
|
|
|
908 |
atIndex:4];
|
|
|
909 |
[invocation invoke];
|
|
|
910 |
}];
|
|
|
911 |
// Call the real implementation if the real App Delegate has any.
|
|
|
912 |
if (didReceiveRemoteNotificationWithCompletionIMP) {
|
|
|
913 |
dispatch_group_enter(callbackGroup);
|
|
|
914 |
|
|
|
915 |
didReceiveRemoteNotificationWithCompletionIMP(self, methodSelector, application, userInfo,
|
|
|
916 |
localCompletionHandler);
|
|
|
917 |
}
|
|
|
918 |
|
|
|
919 |
dispatch_group_notify(callbackGroup, dispatch_get_main_queue(), ^() {
|
|
|
920 |
BOOL allFetchesFailed = YES;
|
|
|
921 |
BOOL anyFetchHasNewData = NO;
|
|
|
922 |
|
|
|
923 |
for (NSNumber *oneResult in fetchResults) {
|
|
|
924 |
UIBackgroundFetchResult result = oneResult.intValue;
|
|
|
925 |
|
|
|
926 |
switch (result) {
|
|
|
927 |
case UIBackgroundFetchResultNoData:
|
|
|
928 |
allFetchesFailed = NO;
|
|
|
929 |
break;
|
|
|
930 |
case UIBackgroundFetchResultNewData:
|
|
|
931 |
allFetchesFailed = NO;
|
|
|
932 |
anyFetchHasNewData = YES;
|
|
|
933 |
break;
|
|
|
934 |
case UIBackgroundFetchResultFailed:
|
|
|
935 |
|
|
|
936 |
break;
|
|
|
937 |
}
|
|
|
938 |
}
|
|
|
939 |
|
|
|
940 |
UIBackgroundFetchResult finalFetchResult = UIBackgroundFetchResultNoData;
|
|
|
941 |
|
|
|
942 |
if (allFetchesFailed) {
|
|
|
943 |
finalFetchResult = UIBackgroundFetchResultFailed;
|
|
|
944 |
} else if (anyFetchHasNewData) {
|
|
|
945 |
finalFetchResult = UIBackgroundFetchResultNewData;
|
|
|
946 |
} else {
|
|
|
947 |
finalFetchResult = UIBackgroundFetchResultNoData;
|
|
|
948 |
}
|
|
|
949 |
|
|
|
950 |
completionHandler(finalFetchResult);
|
|
|
951 |
});
|
|
|
952 |
}
|
|
|
953 |
#endif // !TARGET_OS_WATCH && !TARGET_OS_OSX
|
|
|
954 |
|
|
|
955 |
- (void)application:(GULApplication *)application
|
|
|
956 |
donor_didReceiveRemoteNotification:(NSDictionary *)userInfo {
|
|
|
957 |
SEL methodSelector = NSSelectorFromString(kGULDidReceiveRemoteNotificationSEL);
|
|
|
958 |
NSValue *didReceiveRemoteNotificationIMPPointer =
|
|
|
959 |
[GULAppDelegateSwizzler originalImplementationForSelector:methodSelector object:self];
|
|
|
960 |
GULRealDidReceiveRemoteNotificationIMP didReceiveRemoteNotificationIMP =
|
|
|
961 |
[didReceiveRemoteNotificationIMPPointer pointerValue];
|
|
|
962 |
|
|
|
963 |
// Notify interceptors.
|
|
|
964 |
#pragma clang diagnostic push
|
|
|
965 |
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
|
|
966 |
[GULAppDelegateSwizzler
|
|
|
967 |
notifyInterceptorsWithMethodSelector:methodSelector
|
|
|
968 |
callback:^(id<GULApplicationDelegate> interceptor) {
|
|
|
969 |
NSInvocation *invocation = [GULAppDelegateSwizzler
|
|
|
970 |
appDelegateInvocationForSelector:methodSelector];
|
|
|
971 |
[invocation setTarget:interceptor];
|
|
|
972 |
[invocation setSelector:methodSelector];
|
|
|
973 |
[invocation setArgument:(void *)(&application) atIndex:2];
|
|
|
974 |
[invocation setArgument:(void *)(&userInfo) atIndex:3];
|
|
|
975 |
[invocation invoke];
|
|
|
976 |
}];
|
|
|
977 |
#pragma clang diagnostic pop
|
|
|
978 |
// Call the real implementation if the real App Delegate has any.
|
|
|
979 |
if (didReceiveRemoteNotificationIMP) {
|
|
|
980 |
didReceiveRemoteNotificationIMP(self, methodSelector, application, userInfo);
|
|
|
981 |
}
|
|
|
982 |
}
|
|
|
983 |
|
|
|
984 |
+ (nullable NSInvocation *)appDelegateInvocationForSelector:(SEL)selector {
|
|
|
985 |
struct objc_method_description methodDescription =
|
|
|
986 |
protocol_getMethodDescription(@protocol(GULApplicationDelegate), selector, NO, YES);
|
|
|
987 |
if (methodDescription.types == NULL) {
|
|
|
988 |
return nil;
|
|
|
989 |
}
|
|
|
990 |
|
|
|
991 |
NSMethodSignature *signature = [NSMethodSignature signatureWithObjCTypes:methodDescription.types];
|
|
|
992 |
return [NSInvocation invocationWithMethodSignature:signature];
|
|
|
993 |
}
|
|
|
994 |
|
|
|
995 |
+ (void)proxyAppDelegate:(id<GULApplicationDelegate>)appDelegate {
|
|
|
996 |
if (![appDelegate conformsToProtocol:@protocol(GULApplicationDelegate)]) {
|
|
|
997 |
GULLogNotice(
|
|
|
998 |
kGULLoggerSwizzler, NO,
|
|
|
999 |
[NSString
|
|
|
1000 |
stringWithFormat:@"I-SWZ%06ld",
|
|
|
1001 |
(long)kGULSwizzlerMessageCodeAppDelegateSwizzlingInvalidAppDelegate],
|
|
|
1002 |
@"App Delegate does not conform to UIApplicationDelegate protocol. %@",
|
|
|
1003 |
[GULAppDelegateSwizzler correctAlternativeWhenAppDelegateProxyNotCreated]);
|
|
|
1004 |
return;
|
|
|
1005 |
}
|
|
|
1006 |
|
|
|
1007 |
id<GULApplicationDelegate> originalDelegate = appDelegate;
|
|
|
1008 |
// Do not create a subclass if it is not enabled.
|
|
|
1009 |
if (![GULAppDelegateSwizzler isAppDelegateProxyEnabled]) {
|
|
|
1010 |
GULLogNotice(kGULLoggerSwizzler, NO,
|
|
|
1011 |
[NSString stringWithFormat:@"I-SWZ%06ld",
|
|
|
1012 |
(long)kGULSwizzlerMessageCodeAppDelegateSwizzling011],
|
|
|
1013 |
@"App Delegate Proxy is disabled. %@",
|
|
|
1014 |
[GULAppDelegateSwizzler correctAlternativeWhenAppDelegateProxyNotCreated]);
|
|
|
1015 |
return;
|
|
|
1016 |
}
|
|
|
1017 |
// Do not accept nil delegate.
|
|
|
1018 |
if (!originalDelegate) {
|
|
|
1019 |
GULLogError(kGULLoggerSwizzler, NO,
|
|
|
1020 |
[NSString stringWithFormat:@"I-SWZ%06ld",
|
|
|
1021 |
(long)kGULSwizzlerMessageCodeAppDelegateSwizzling012],
|
|
|
1022 |
@"Cannot create App Delegate Proxy because App Delegate instance is nil. %@",
|
|
|
1023 |
[GULAppDelegateSwizzler correctAlternativeWhenAppDelegateProxyNotCreated]);
|
|
|
1024 |
return;
|
|
|
1025 |
}
|
|
|
1026 |
|
|
|
1027 |
@try {
|
|
|
1028 |
gOriginalAppDelegateClass = [originalDelegate class];
|
|
|
1029 |
gAppDelegateSubclass = [self createSubclassWithObject:originalDelegate];
|
|
|
1030 |
[self reassignAppDelegate];
|
|
|
1031 |
} @catch (NSException *exception) {
|
|
|
1032 |
GULLogError(kGULLoggerSwizzler, NO,
|
|
|
1033 |
[NSString stringWithFormat:@"I-SWZ%06ld",
|
|
|
1034 |
(long)kGULSwizzlerMessageCodeAppDelegateSwizzling013],
|
|
|
1035 |
@"Cannot create App Delegate Proxy. %@",
|
|
|
1036 |
[GULAppDelegateSwizzler correctAlternativeWhenAppDelegateProxyNotCreated]);
|
|
|
1037 |
return;
|
|
|
1038 |
}
|
|
|
1039 |
}
|
|
|
1040 |
|
|
|
1041 |
#pragma mark - Methods to print correct debug logs
|
|
|
1042 |
|
|
|
1043 |
+ (NSString *)correctAppDelegateProxyKey {
|
|
|
1044 |
return NSClassFromString(@"FIRCore") ? kGULFirebaseAppDelegateProxyEnabledPlistKey
|
|
|
1045 |
: kGULGoogleUtilitiesAppDelegateProxyEnabledPlistKey;
|
|
|
1046 |
}
|
|
|
1047 |
|
|
|
1048 |
+ (NSString *)correctAlternativeWhenAppDelegateProxyNotCreated {
|
|
|
1049 |
return NSClassFromString(@"FIRCore")
|
|
|
1050 |
? @"To log deep link campaigns manually, call the methods in "
|
|
|
1051 |
@"FIRAnalytics+AppDelegate.h."
|
|
|
1052 |
: @"";
|
|
|
1053 |
}
|
|
|
1054 |
|
|
|
1055 |
#pragma mark - Private Methods for Testing
|
|
|
1056 |
|
|
|
1057 |
+ (void)clearInterceptors {
|
|
|
1058 |
[[self interceptors] removeAllObjects];
|
|
|
1059 |
}
|
|
|
1060 |
|
|
|
1061 |
+ (void)resetProxyOriginalDelegateOnceToken {
|
|
|
1062 |
sProxyAppDelegateOnceToken = 0;
|
|
|
1063 |
sProxyAppDelegateRemoteNotificationOnceToken = 0;
|
|
|
1064 |
}
|
|
|
1065 |
|
|
|
1066 |
+ (id<GULApplicationDelegate>)originalDelegate {
|
|
|
1067 |
return gOriginalAppDelegate;
|
|
|
1068 |
}
|
|
|
1069 |
|
|
|
1070 |
@end
|