Proyectos de Subversion Iphone Microlearning

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
// Copyright 2019 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/Public/GoogleUtilities/GULSceneDelegateSwizzler.h"
18
 
19
#import "GoogleUtilities/AppDelegateSwizzler/Internal/GULSceneDelegateSwizzler_Private.h"
20
#import "GoogleUtilities/AppDelegateSwizzler/Public/GoogleUtilities/GULAppDelegateSwizzler.h"
21
#import "GoogleUtilities/Common/GULLoggerCodes.h"
22
#import "GoogleUtilities/Environment/Public/GoogleUtilities/GULAppEnvironmentUtil.h"
23
#import "GoogleUtilities/Logger/Public/GoogleUtilities/GULLogger.h"
24
#import "GoogleUtilities/Network/Public/GoogleUtilities/GULMutableDictionary.h"
25
 
26
#import <objc/runtime.h>
27
 
28
#if UISCENE_SUPPORTED
29
API_AVAILABLE(ios(13.0), tvos(13.0))
30
typedef void (*GULOpenURLContextsIMP)(id, SEL, UIScene *, NSSet<UIOpenURLContext *> *);
31
 
32
API_AVAILABLE(ios(13.0), tvos(13.0))
33
typedef void (^GULSceneDelegateInterceptorCallback)(id<UISceneDelegate>);
34
 
35
// The strings below are the keys for associated objects.
36
static char const *const kGULRealIMPBySelectorKey = "GUL_realIMPBySelector";
37
static char const *const kGULRealClassKey = "GUL_realClass";
38
#endif  // UISCENE_SUPPORTED
39
 
40
static GULLoggerService kGULLoggerSwizzler = @"[GoogleUtilities/SceneDelegateSwizzler]";
41
 
42
// Since Firebase SDKs also use this for app delegate proxying, in order to not be a breaking change
43
// we disable App Delegate proxying when either of these two flags are set to NO.
44
 
45
/** Plist key that allows Firebase developers to disable App and Scene Delegate Proxying. */
46
static NSString *const kGULFirebaseSceneDelegateProxyEnabledPlistKey =
47
    @"FirebaseAppDelegateProxyEnabled";
48
 
49
/** Plist key that allows developers not using Firebase to disable App and Scene Delegate Proxying.
50
 */
51
static NSString *const kGULGoogleUtilitiesSceneDelegateProxyEnabledPlistKey =
52
    @"GoogleUtilitiesAppDelegateProxyEnabled";
53
 
54
/** The prefix of the Scene Delegate. */
55
static NSString *const kGULSceneDelegatePrefix = @"GUL_";
56
 
57
/**
58
 * This class is necessary to store the delegates in an NSArray without retaining them.
59
 * [NSValue valueWithNonRetainedObject] also provides this functionality, but does not provide a
60
 * zeroing pointer. This will cause EXC_BAD_ACCESS when trying to access the object after it is
61
 * dealloced. Instead, this container stores a weak, zeroing reference to the object, which
62
 * automatically is set to nil by the runtime when the object is dealloced.
63
 */
64
@interface GULSceneZeroingWeakContainer : NSObject
65
 
66
/** Stores a weak object. */
67
@property(nonatomic, weak) id object;
68
 
69
@end
70
 
71
@implementation GULSceneZeroingWeakContainer
72
@end
73
 
74
@implementation GULSceneDelegateSwizzler
75
 
76
#pragma mark - Public methods
77
 
78
+ (BOOL)isSceneDelegateProxyEnabled {
79
  return [GULAppDelegateSwizzler isAppDelegateProxyEnabled];
80
}
81
 
82
+ (void)proxyOriginalSceneDelegate {
83
#if UISCENE_SUPPORTED
84
  if ([GULAppEnvironmentUtil isAppExtension]) {
85
    return;
86
  }
87
 
88
  static dispatch_once_t onceToken;
89
  dispatch_once(&onceToken, ^{
90
    if (@available(iOS 13.0, tvOS 13.0, *)) {
91
      if (![GULSceneDelegateSwizzler isSceneDelegateProxyEnabled]) {
92
        return;
93
      }
94
      [[NSNotificationCenter defaultCenter]
95
          addObserver:self
96
             selector:@selector(handleSceneWillConnectToNotification:)
97
                 name:UISceneWillConnectNotification
98
               object:nil];
99
    }
100
  });
101
#endif  // UISCENE_SUPPORTED
102
}
103
 
104
#if UISCENE_SUPPORTED
105
+ (GULSceneDelegateInterceptorID)registerSceneDelegateInterceptor:(id<UISceneDelegate>)interceptor {
106
  NSAssert(interceptor, @"SceneDelegateProxy cannot add nil interceptor");
107
  NSAssert([interceptor conformsToProtocol:@protocol(UISceneDelegate)],
108
           @"SceneDelegateProxy interceptor does not conform to UIApplicationDelegate");
109
 
110
  if (!interceptor) {
111
    GULLogError(kGULLoggerSwizzler, NO,
112
                [NSString stringWithFormat:@"I-SWZ%06ld",
113
                                           (long)kGULSwizzlerMessageCodeSceneDelegateSwizzling000],
114
                @"SceneDelegateProxy cannot add nil interceptor.");
115
    return nil;
116
  }
117
  if (![interceptor conformsToProtocol:@protocol(UISceneDelegate)]) {
118
    GULLogError(kGULLoggerSwizzler, NO,
119
                [NSString stringWithFormat:@"I-SWZ%06ld",
120
                                           (long)kGULSwizzlerMessageCodeSceneDelegateSwizzling001],
121
                @"SceneDelegateProxy interceptor does not conform to UIApplicationDelegate");
122
    return nil;
123
  }
124
 
125
  // The ID should be the same given the same interceptor object.
126
  NSString *interceptorID =
127
      [NSString stringWithFormat:@"%@%p", kGULSceneDelegatePrefix, interceptor];
128
  if (!interceptorID.length) {
129
    GULLogError(kGULLoggerSwizzler, NO,
130
                [NSString stringWithFormat:@"I-SWZ%06ld",
131
                                           (long)kGULSwizzlerMessageCodeSceneDelegateSwizzling002],
132
                @"SceneDelegateProxy cannot create Interceptor ID.");
133
    return nil;
134
  }
135
  GULSceneZeroingWeakContainer *weakObject = [[GULSceneZeroingWeakContainer alloc] init];
136
  weakObject.object = interceptor;
137
  [GULSceneDelegateSwizzler interceptors][interceptorID] = weakObject;
138
  return interceptorID;
139
}
140
 
141
+ (void)unregisterSceneDelegateInterceptorWithID:(GULSceneDelegateInterceptorID)interceptorID {
142
  NSAssert(interceptorID, @"SceneDelegateProxy cannot unregister nil interceptor ID.");
143
  NSAssert(((NSString *)interceptorID).length != 0,
144
           @"SceneDelegateProxy cannot unregister empty interceptor ID.");
145
 
146
  if (!interceptorID) {
147
    GULLogError(kGULLoggerSwizzler, NO,
148
                [NSString stringWithFormat:@"I-SWZ%06ld",
149
                                           (long)kGULSwizzlerMessageCodeSceneDelegateSwizzling003],
150
                @"SceneDelegateProxy cannot unregister empty interceptor ID.");
151
    return;
152
  }
153
 
154
  GULSceneZeroingWeakContainer *weakContainer =
155
      [GULSceneDelegateSwizzler interceptors][interceptorID];
156
  if (!weakContainer.object) {
157
    GULLogError(kGULLoggerSwizzler, NO,
158
                [NSString stringWithFormat:@"I-SWZ%06ld",
159
                                           (long)kGULSwizzlerMessageCodeSceneDelegateSwizzling004],
160
                @"SceneDelegateProxy cannot unregister interceptor that was not registered. "
161
                 "Interceptor ID %@",
162
                interceptorID);
163
    return;
164
  }
165
 
166
  [[GULSceneDelegateSwizzler interceptors] removeObjectForKey:interceptorID];
167
}
168
 
169
#pragma mark - Helper methods
170
 
171
+ (GULMutableDictionary *)interceptors {
172
  static dispatch_once_t onceToken;
173
  static GULMutableDictionary *sInterceptors;
174
  dispatch_once(&onceToken, ^{
175
    sInterceptors = [[GULMutableDictionary alloc] init];
176
  });
177
  return sInterceptors;
178
}
179
 
180
+ (void)clearInterceptors {
181
  [[self interceptors] removeAllObjects];
182
}
183
 
184
+ (nullable NSValue *)originalImplementationForSelector:(SEL)selector object:(id)object {
185
  NSDictionary *realImplementationBySelector =
186
      objc_getAssociatedObject(object, &kGULRealIMPBySelectorKey);
187
  return realImplementationBySelector[NSStringFromSelector(selector)];
188
}
189
 
190
+ (void)proxyDestinationSelector:(SEL)destinationSelector
191
    implementationsFromSourceSelector:(SEL)sourceSelector
192
                            fromClass:(Class)sourceClass
193
                              toClass:(Class)destinationClass
194
                            realClass:(Class)realClass
195
     storeDestinationImplementationTo:
196
         (NSMutableDictionary<NSString *, NSValue *> *)destinationImplementationsBySelector {
197
  [self addInstanceMethodWithDestinationSelector:destinationSelector
198
            withImplementationFromSourceSelector:sourceSelector
199
                                       fromClass:sourceClass
200
                                         toClass:destinationClass];
201
  IMP sourceImplementation =
202
      [GULSceneDelegateSwizzler implementationOfMethodSelector:destinationSelector
203
                                                     fromClass:realClass];
204
  NSValue *sourceImplementationPointer = [NSValue valueWithPointer:sourceImplementation];
205
 
206
  NSString *destinationSelectorString = NSStringFromSelector(destinationSelector);
207
  destinationImplementationsBySelector[destinationSelectorString] = sourceImplementationPointer;
208
}
209
 
210
/** Copies a method identified by the methodSelector from one class to the other. After this method
211
 *  is called, performing [toClassInstance methodSelector] will be similar to calling
212
 *  [fromClassInstance methodSelector]. This method does nothing if toClass already has a method
213
 *  identified by methodSelector.
214
 *
215
 *  @param methodSelector The SEL that identifies both the method on the fromClass as well as the
216
 *      one on the toClass.
217
 *  @param fromClass The class from which a method is sourced.
218
 *  @param toClass The class to which the method is added. If the class already has a method with
219
 *      the same selector, this has no effect.
220
 */
221
+ (void)addInstanceMethodWithSelector:(SEL)methodSelector
222
                            fromClass:(Class)fromClass
223
                              toClass:(Class)toClass {
224
  [self addInstanceMethodWithDestinationSelector:methodSelector
225
            withImplementationFromSourceSelector:methodSelector
226
                                       fromClass:fromClass
227
                                         toClass:toClass];
228
}
229
 
230
/** Copies a method identified by the sourceSelector from the fromClass as a method for the
231
 *  destinationSelector on the toClass. After this method is called, performing
232
 *  [toClassInstance destinationSelector] will be similar to calling
233
 *  [fromClassInstance sourceSelector]. This method does nothing if toClass already has a method
234
 *  identified by destinationSelector.
235
 *
236
 *  @param destinationSelector The SEL that identifies the method on the toClass.
237
 *  @param sourceSelector The SEL that identifies the method on the fromClass.
238
 *  @param fromClass The class from which a method is sourced.
239
 *  @param toClass The class to which the method is added. If the class already has a method with
240
 *      the same selector, this has no effect.
241
 */
242
+ (void)addInstanceMethodWithDestinationSelector:(SEL)destinationSelector
243
            withImplementationFromSourceSelector:(SEL)sourceSelector
244
                                       fromClass:(Class)fromClass
245
                                         toClass:(Class)toClass {
246
  Method method = class_getInstanceMethod(fromClass, sourceSelector);
247
  IMP methodIMP = method_getImplementation(method);
248
  const char *types = method_getTypeEncoding(method);
249
  if (!class_addMethod(toClass, destinationSelector, methodIMP, types)) {
250
    GULLogWarning(
251
        kGULLoggerSwizzler, NO,
252
        [NSString
253
            stringWithFormat:@"I-SWZ%06ld", (long)kGULSwizzlerMessageCodeSceneDelegateSwizzling009],
254
        @"Cannot copy method to destination selector %@ as it already exists",
255
        NSStringFromSelector(destinationSelector));
256
  }
257
}
258
 
259
/** Gets the IMP of the instance method on the class identified by the selector.
260
 *
261
 *  @param selector The selector of which the IMP is to be fetched.
262
 *  @param aClass The class from which the IMP is to be fetched.
263
 *  @return The IMP of the instance method identified by selector and aClass.
264
 */
265
+ (IMP)implementationOfMethodSelector:(SEL)selector fromClass:(Class)aClass {
266
  Method aMethod = class_getInstanceMethod(aClass, selector);
267
  return method_getImplementation(aMethod);
268
}
269
 
270
/** Enumerates through all the interceptors and if they respond to a given selector, executes a
271
 *  GULSceneDelegateInterceptorCallback with the interceptor.
272
 *
273
 *  @param methodSelector The SEL to check if an interceptor responds to.
274
 *  @param callback the GULSceneDelegateInterceptorCallback.
275
 */
276
+ (void)notifyInterceptorsWithMethodSelector:(SEL)methodSelector
277
                                    callback:(GULSceneDelegateInterceptorCallback)callback
278
    API_AVAILABLE(ios(13.0)) {
279
  if (!callback) {
280
    return;
281
  }
282
 
283
  NSDictionary *interceptors = [GULSceneDelegateSwizzler interceptors].dictionary;
284
  [interceptors enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
285
    GULSceneZeroingWeakContainer *interceptorContainer = obj;
286
    id interceptor = interceptorContainer.object;
287
    if (!interceptor) {
288
      GULLogWarning(
289
          kGULLoggerSwizzler, NO,
290
          [NSString stringWithFormat:@"I-SWZ%06ld",
291
                                     (long)kGULSwizzlerMessageCodeSceneDelegateSwizzling010],
292
          @"SceneDelegateProxy cannot find interceptor with ID %@. Removing the interceptor.", key);
293
      [[GULSceneDelegateSwizzler interceptors] removeObjectForKey:key];
294
      return;
295
    }
296
    if ([interceptor respondsToSelector:methodSelector]) {
297
      callback(interceptor);
298
    }
299
  }];
300
}
301
 
302
+ (void)handleSceneWillConnectToNotification:(NSNotification *)notification {
303
  if (@available(iOS 13.0, tvOS 13.0, *)) {
304
    if ([notification.object isKindOfClass:[UIScene class]]) {
305
      UIScene *scene = (UIScene *)notification.object;
306
      [GULSceneDelegateSwizzler proxySceneDelegateIfNeeded:scene];
307
    }
308
  }
309
}
310
 
311
#pragma mark - [Donor Methods] UISceneDelegate URL handler
312
 
313
- (void)scene:(UIScene *)scene
314
    openURLContexts:(NSSet<UIOpenURLContext *> *)URLContexts API_AVAILABLE(ios(13.0), tvos(13.0)) {
315
  if (@available(iOS 13.0, tvOS 13.0, *)) {
316
    SEL methodSelector = @selector(scene:openURLContexts:);
317
    // Call the real implementation if the real Scene Delegate has any.
318
    NSValue *openURLContextsIMPPointer =
319
        [GULSceneDelegateSwizzler originalImplementationForSelector:methodSelector object:self];
320
    GULOpenURLContextsIMP openURLContextsIMP = [openURLContextsIMPPointer pointerValue];
321
 
322
    [GULSceneDelegateSwizzler
323
        notifyInterceptorsWithMethodSelector:methodSelector
324
                                    callback:^(id<UISceneDelegate> interceptor) {
325
                                      if ([interceptor
326
                                              conformsToProtocol:@protocol(UISceneDelegate)]) {
327
                                        id<UISceneDelegate> sceneInterceptor =
328
                                            (id<UISceneDelegate>)interceptor;
329
                                        [sceneInterceptor scene:scene openURLContexts:URLContexts];
330
                                      }
331
                                    }];
332
 
333
    if (openURLContextsIMP) {
334
      openURLContextsIMP(self, methodSelector, scene, URLContexts);
335
    }
336
  }
337
}
338
 
339
+ (void)proxySceneDelegateIfNeeded:(UIScene *)scene {
340
  Class realClass = [scene.delegate class];
341
  NSString *className = NSStringFromClass(realClass);
342
 
343
  // Skip proxying if failed to get the delegate class name for some reason (e.g. `delegate == nil`)
344
  // or the class has a prefix of kGULAppDelegatePrefix, which means it has been proxied before.
345
  if (className == nil || [className hasPrefix:kGULSceneDelegatePrefix]) {
346
    return;
347
  }
348
 
349
  NSString *classNameWithPrefix = [kGULSceneDelegatePrefix stringByAppendingString:className];
350
  NSString *newClassName =
351
      [NSString stringWithFormat:@"%@-%@", classNameWithPrefix, [NSUUID UUID].UUIDString];
352
 
353
  if (NSClassFromString(newClassName)) {
354
    GULLogError(
355
        kGULLoggerSwizzler, NO,
356
        [NSString
357
            stringWithFormat:@"I-SWZ%06ld",
358
                             (long)
359
                                 kGULSwizzlerMessageCodeSceneDelegateSwizzlingInvalidSceneDelegate],
360
        @"Cannot create a proxy for Scene Delegate. Subclass already exists. Original Class"
361
        @": %@, subclass: %@",
362
        className, newClassName);
363
    return;
364
  }
365
 
366
  // Register the new class as subclass of the real one. Do not allocate more than the real class
367
  // size.
368
  Class sceneDelegateSubClass = objc_allocateClassPair(realClass, newClassName.UTF8String, 0);
369
  if (sceneDelegateSubClass == Nil) {
370
    GULLogError(
371
        kGULLoggerSwizzler, NO,
372
        [NSString
373
            stringWithFormat:@"I-SWZ%06ld",
374
                             (long)
375
                                 kGULSwizzlerMessageCodeSceneDelegateSwizzlingInvalidSceneDelegate],
376
        @"Cannot create a proxy for Scene Delegate. Subclass already exists. Original Class"
377
        @": %@, subclass: Nil",
378
        className);
379
    return;
380
  }
381
 
382
  NSMutableDictionary<NSString *, NSValue *> *realImplementationsBySelector =
383
      [[NSMutableDictionary alloc] init];
384
 
385
  // For scene:openURLContexts:
386
  SEL openURLContextsSEL = @selector(scene:openURLContexts:);
387
  [self proxyDestinationSelector:openURLContextsSEL
388
      implementationsFromSourceSelector:openURLContextsSEL
389
                              fromClass:[GULSceneDelegateSwizzler class]
390
                                toClass:sceneDelegateSubClass
391
                              realClass:realClass
392
       storeDestinationImplementationTo:realImplementationsBySelector];
393
 
394
  // Store original implementations to a fake property of the original delegate.
395
  objc_setAssociatedObject(scene.delegate, &kGULRealIMPBySelectorKey,
396
                           [realImplementationsBySelector copy], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
397
  objc_setAssociatedObject(scene.delegate, &kGULRealClassKey, realClass,
398
                           OBJC_ASSOCIATION_RETAIN_NONATOMIC);
399
 
400
  // The subclass size has to be exactly the same size with the original class size. The subclass
401
  // cannot have more ivars/properties than its superclass since it will cause an offset in memory
402
  // that can lead to overwriting the isa of an object in the next frame.
403
  if (class_getInstanceSize(realClass) != class_getInstanceSize(sceneDelegateSubClass)) {
404
    GULLogError(
405
        kGULLoggerSwizzler, NO,
406
        [NSString
407
            stringWithFormat:@"I-SWZ%06ld",
408
                             (long)
409
                                 kGULSwizzlerMessageCodeSceneDelegateSwizzlingInvalidSceneDelegate],
410
        @"Cannot create subclass of Scene Delegate, because the created subclass is not the "
411
        @"same size. %@",
412
        className);
413
    NSAssert(NO, @"Classes must be the same size to swizzle isa");
414
    return;
415
  }
416
 
417
  // Make the newly created class to be the subclass of the real Scene Delegate class.
418
  objc_registerClassPair(sceneDelegateSubClass);
419
  if (object_setClass(scene.delegate, sceneDelegateSubClass)) {
420
    GULLogDebug(
421
        kGULLoggerSwizzler, NO,
422
        [NSString
423
            stringWithFormat:@"I-SWZ%06ld",
424
                             (long)
425
                                 kGULSwizzlerMessageCodeSceneDelegateSwizzlingInvalidSceneDelegate],
426
        @"Successfully created Scene Delegate Proxy automatically. To disable the "
427
        @"proxy, set the flag %@ to NO (Boolean) in the Info.plist",
428
        [GULSceneDelegateSwizzler correctSceneDelegateProxyKey]);
429
  }
430
}
431
 
432
+ (NSString *)correctSceneDelegateProxyKey {
433
  return NSClassFromString(@"FIRCore") ? kGULFirebaseSceneDelegateProxyEnabledPlistKey
434
                                       : kGULGoogleUtilitiesSceneDelegateProxyEnabledPlistKey;
435
}
436
 
437
#endif  // UISCENE_SUPPORTED
438
 
439
@end