1// Copyright © 2018 650 Industries. All rights reserved.
2
3#import <Foundation/FoundationErrors.h>
4
5#import <ExpoModulesCore/EXModuleRegistryProvider.h>
6#import <ExpoModulesCore/EXLegacyAppDelegateWrapper.h>
7
8static NSMutableArray<id<UIApplicationDelegate>> *subcontractors;
9static NSMutableDictionary<NSString *,NSArray<id<UIApplicationDelegate>> *> *subcontractorsForSelector;
10static dispatch_once_t onceToken;
11
12@implementation EXLegacyAppDelegateWrapper
13
14@synthesize window = _window;
15
16- (void)forwardInvocation:(NSInvocation *)invocation {
17#if DEBUG
18  SEL selector = [invocation selector];
19  NSArray<id<UIApplicationDelegate>> *delegatesToBeCalled = [self getSubcontractorsImplementingSelector:selector];
20  NSString *selectorName = NSStringFromSelector(selector);
21  if ([delegatesToBeCalled count] > 0) {
22    [NSException raise:@"Method not implemented in UIApplicationDelegate" format:@"Some modules: %@ have registered for `%@` UIApplicationDelegate's callback, however, neither your AppDelegate nor %@ can handle this method. You'll need to either implement this method in your AppDelegate or submit a pull request to handle it in %@.", delegatesToBeCalled, selectorName, NSStringFromClass([self class]), NSStringFromClass([self class])];
23  }
24#endif
25
26  [super forwardInvocation:invocation];
27}
28
29- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(nullable NSDictionary *)launchOptions
30{
31  BOOL answer = NO;
32
33  SEL selector = @selector(application:didFinishLaunchingWithOptions:);
34  NSArray<id<UIApplicationDelegate>> *subcontractorsArray = [self getSubcontractorsImplementingSelector:selector];
35
36  for (id<UIApplicationDelegate> subcontractor in subcontractorsArray) {
37    BOOL subcontractorAnswer = NO;
38    subcontractorAnswer = [subcontractor application:application didFinishLaunchingWithOptions:launchOptions];
39    answer |= subcontractorAnswer;
40  }
41
42  return answer;
43}
44
45- (void)applicationWillEnterForeground:(UIApplication *)application
46{
47  SEL selector = @selector(applicationWillEnterForeground:);
48  NSArray<id<UIApplicationDelegate>> *subcontractorsArray = [self getSubcontractorsImplementingSelector:selector];
49
50  for (id<UIApplicationDelegate> subcontractor in subcontractorsArray) {
51    [subcontractor applicationWillEnterForeground:application];
52  }
53}
54
55- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options
56{
57
58  SEL selector = @selector(application:openURL:options:);
59  NSArray<id<UIApplicationDelegate>> *subcontractorsArray = [self getSubcontractorsImplementingSelector:selector];
60
61  for (id<UIApplicationDelegate> subcontractor in subcontractorsArray) {
62    if ([subcontractor application:app openURL:url options:options]) {
63      return YES;
64    }
65  }
66
67  return NO;
68}
69
70- (void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
71{
72  SEL selector = @selector(application:performFetchWithCompletionHandler:);
73  NSArray<id<UIApplicationDelegate>> *subcontractorsArray = [self getSubcontractorsImplementingSelector:selector];
74
75  __block NSUInteger subcontractorsLeft = [subcontractorsArray count];
76  __block UIBackgroundFetchResult fetchResult = UIBackgroundFetchResultNoData;
77  __block NSObject *lock = [NSObject new];
78
79  void (^handler)(UIBackgroundFetchResult) = ^(UIBackgroundFetchResult result) {
80    @synchronized (lock) {
81      if (result == UIBackgroundFetchResultFailed) {
82        fetchResult = UIBackgroundFetchResultFailed;
83      } else if (fetchResult != UIBackgroundFetchResultFailed && result == UIBackgroundFetchResultNewData) {
84        fetchResult = UIBackgroundFetchResultNewData;
85      }
86
87      subcontractorsLeft--;
88      if (subcontractorsLeft == 0) {
89        completionHandler(fetchResult);
90      }
91    }
92  };
93
94  if (subcontractorsLeft == 0) {
95    completionHandler(fetchResult);
96  } else {
97    for (id<UIApplicationDelegate> subcontractor in subcontractorsArray) {
98      [subcontractor application:application performFetchWithCompletionHandler:handler];
99    }
100  }
101}
102
103- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray<id<UIUserActivityRestoring>> * _Nullable))restorationHandler
104{
105  SEL selector = @selector(application:continueUserActivity:restorationHandler:);
106  NSArray<id<UIApplicationDelegate>> *subcontractorsArray = [self getSubcontractorsImplementingSelector:selector];
107
108  __block NSMutableArray<id<UIUserActivityRestoring>> * _Nullable mergedParams = [NSMutableArray new];
109  __block NSUInteger subcontractorsLeft = [subcontractorsArray count];
110  __block NSObject *lock = [NSObject new];
111
112  void (^handler)(NSArray<id<UIUserActivityRestoring>> * _Nullable) = ^(NSArray<id<UIUserActivityRestoring>> * _Nullable param) {
113    @synchronized (lock) {
114
115      [mergedParams addObjectsFromArray:param];
116
117      subcontractorsLeft--;
118      if (subcontractorsLeft == 0) {
119        restorationHandler(mergedParams);
120      }
121    }
122  };
123
124   BOOL result = NO;
125
126  for (id<UIApplicationDelegate> subcontractor in subcontractorsArray) {
127    result = result || [subcontractor application:application continueUserActivity:userActivity restorationHandler:handler];
128  }
129  return result;
130}
131
132#pragma mark - BackgroundSession
133
134- (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)(void))completionHandler
135{
136  SEL selector = @selector(application:handleEventsForBackgroundURLSession:completionHandler:);
137  NSArray<id<UIApplicationDelegate>> *subcontractorsArray = [self getSubcontractorsImplementingSelector:selector];
138
139  __block BOOL delegatingCompleted = NO;
140  __block int delegatesCompleted = 0;
141  __block unsigned long allDelegates = subcontractorsArray.count;
142  __block void (^completionHandlerCaller)(void) = ^ {
143    if (delegatesCompleted && delegatingCompleted == allDelegates) {
144      completionHandler();
145    }
146  };
147
148  for (id<UIApplicationDelegate> subcontractor in subcontractorsArray) {
149    [subcontractor application:application handleEventsForBackgroundURLSession:identifier completionHandler:^(){
150      @synchronized (self) {
151        delegatesCompleted += 1;
152        completionHandlerCaller();
153      }
154    }];
155  }
156
157  @synchronized (self) {
158    delegatingCompleted = YES;
159    completionHandlerCaller();
160  }
161}
162
163#pragma mark - Notifications
164
165- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)token
166{
167  SEL selector = @selector(application:didRegisterForRemoteNotificationsWithDeviceToken:);
168  NSArray<id<UIApplicationDelegate>> *subcontractorsArray = [self getSubcontractorsImplementingSelector:selector];
169
170  for (id<UIApplicationDelegate> subcontractor in subcontractorsArray) {
171    [subcontractor application:application didRegisterForRemoteNotificationsWithDeviceToken:token];
172  }
173}
174
175- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)err
176{
177  SEL selector = @selector(application:didFailToRegisterForRemoteNotificationsWithError:);
178  NSArray<id<UIApplicationDelegate>> *subcontractorsArray = [self getSubcontractorsImplementingSelector:selector];
179
180  for(id<UIApplicationDelegate> subcontractor in subcontractorsArray) {
181    [subcontractor application:application didFailToRegisterForRemoteNotificationsWithError:err];
182  }
183}
184
185- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler
186{
187  SEL selector = @selector(application:didReceiveRemoteNotification:fetchCompletionHandler:);
188  NSArray<id<UIApplicationDelegate>> *subcontractorsArray = [self getSubcontractorsImplementingSelector:selector];
189
190  __block NSUInteger subcontractorsLeft = [subcontractorsArray count];
191  __block UIBackgroundFetchResult fetchResult = UIBackgroundFetchResultNoData;
192  __block NSObject *lock = [NSObject new];
193
194  void (^handler)(UIBackgroundFetchResult) = ^(UIBackgroundFetchResult result) {
195    @synchronized (lock) {
196      if (result == UIBackgroundFetchResultFailed) {
197        fetchResult = UIBackgroundFetchResultFailed;
198      } else if (fetchResult != UIBackgroundFetchResultFailed && result == UIBackgroundFetchResultNewData) {
199        fetchResult = UIBackgroundFetchResultNewData;
200      }
201
202      subcontractorsLeft--;
203      if (subcontractorsLeft == 0) {
204        completionHandler(fetchResult);
205      }
206    }
207  };
208
209  if (subcontractorsLeft == 0) {
210    completionHandler(fetchResult);
211  } else {
212    for (id<UIApplicationDelegate> subcontractor in subcontractorsArray) {
213      [subcontractor application:application didReceiveRemoteNotification:userInfo fetchCompletionHandler:handler];
214    }
215  }
216}
217
218
219#pragma mark - Subcontractors
220
221- (void)ensureSubcontractorsAreInitializedAndSorted {
222  dispatch_once(&onceToken, ^{
223    subcontractors = [[NSMutableArray alloc] init];
224    subcontractorsForSelector = [NSMutableDictionary new];
225
226    NSArray<EXSingletonModule*> * singletonModules = [[EXModuleRegistryProvider singletonModules] allObjects];
227
228    for (EXSingletonModule *singletonModule in singletonModules) {
229      if ([singletonModule conformsToProtocol:@protocol(UIApplicationDelegate)]) {
230        [subcontractors addObject:(id<UIApplicationDelegate>)singletonModule];
231      }
232    }
233
234    NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"priority"
235                                                                   ascending:NO];
236    [subcontractors sortUsingDescriptors:[NSArray arrayWithObject:sortDescriptor]];
237  });
238}
239
240- (NSArray<id<UIApplicationDelegate>> *)getSubcontractorsImplementingSelector:(SEL)selector {
241
242  [self ensureSubcontractorsAreInitializedAndSorted];
243
244  NSString *selectorKey = NSStringFromSelector(selector);
245
246  if (subcontractorsForSelector[selectorKey]) {
247    return subcontractorsForSelector[selectorKey];
248  }
249
250  NSMutableArray<id<UIApplicationDelegate>> *result = [NSMutableArray new];
251
252  for (id<UIApplicationDelegate> subcontractor in subcontractors) {
253    if ([subcontractor respondsToSelector:selector]) {
254      [result addObject:subcontractor];
255    }
256  }
257
258  subcontractorsForSelector[selectorKey] = result;
259
260  return result;
261}
262
263@end
264