// Copyright © 2018 650 Industries. All rights reserved. #import #import #import #import #import #if !TARGET_OS_OSX static NSMutableArray> *subcontractors; static NSMutableDictionary> *> *subcontractorsForSelector; static dispatch_once_t onceToken; #endif @implementation EXLegacyAppDelegateWrapper // The legacy app delegate wrapper is not supported on macOS, but we keep it no-op for convenience. #if !TARGET_OS_OSX @synthesize window = _window; - (void)forwardInvocation:(NSInvocation *)invocation { #if DEBUG SEL selector = [invocation selector]; NSArray> *delegatesToBeCalled = [self getSubcontractorsImplementingSelector:selector]; NSString *selectorName = NSStringFromSelector(selector); if ([delegatesToBeCalled count] > 0) { [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])]; } #endif [super forwardInvocation:invocation]; } - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(nullable NSDictionary *)launchOptions { BOOL answer = NO; SEL selector = @selector(application:didFinishLaunchingWithOptions:); NSArray> *subcontractorsArray = [self getSubcontractorsImplementingSelector:selector]; for (id subcontractor in subcontractorsArray) { BOOL subcontractorAnswer = NO; subcontractorAnswer = [subcontractor application:application didFinishLaunchingWithOptions:launchOptions]; answer |= subcontractorAnswer; } return answer; } - (void)applicationWillEnterForeground:(UIApplication *)application { SEL selector = @selector(applicationWillEnterForeground:); NSArray> *subcontractorsArray = [self getSubcontractorsImplementingSelector:selector]; for (id subcontractor in subcontractorsArray) { [subcontractor applicationWillEnterForeground:application]; } } - (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary *)options { SEL selector = @selector(application:openURL:options:); NSArray> *subcontractorsArray = [self getSubcontractorsImplementingSelector:selector]; for (id subcontractor in subcontractorsArray) { if ([subcontractor application:app openURL:url options:options]) { return YES; } } return NO; } - (void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { SEL selector = @selector(application:performFetchWithCompletionHandler:); NSArray> *subcontractorsArray = [self getSubcontractorsImplementingSelector:selector]; __block NSUInteger subcontractorsLeft = [subcontractorsArray count]; __block UIBackgroundFetchResult fetchResult = UIBackgroundFetchResultNoData; __block NSObject *lock = [NSObject new]; void (^handler)(UIBackgroundFetchResult) = ^(UIBackgroundFetchResult result) { @synchronized (lock) { if (result == UIBackgroundFetchResultFailed) { fetchResult = UIBackgroundFetchResultFailed; } else if (fetchResult != UIBackgroundFetchResultFailed && result == UIBackgroundFetchResultNewData) { fetchResult = UIBackgroundFetchResultNewData; } subcontractorsLeft--; if (subcontractorsLeft == 0) { completionHandler(fetchResult); } } }; if (subcontractorsLeft == 0) { completionHandler(fetchResult); } else { for (id subcontractor in subcontractorsArray) { [subcontractor application:application performFetchWithCompletionHandler:handler]; } } } - (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray> * _Nullable))restorationHandler { SEL selector = @selector(application:continueUserActivity:restorationHandler:); NSArray> *subcontractorsArray = [self getSubcontractorsImplementingSelector:selector]; __block NSMutableArray> * _Nullable mergedParams = [NSMutableArray new]; __block NSUInteger subcontractorsLeft = [subcontractorsArray count]; __block NSObject *lock = [NSObject new]; void (^handler)(NSArray> * _Nullable) = ^(NSArray> * _Nullable param) { @synchronized (lock) { [mergedParams addObjectsFromArray:param]; subcontractorsLeft--; if (subcontractorsLeft == 0) { restorationHandler(mergedParams); } } }; BOOL result = NO; for (id subcontractor in subcontractorsArray) { result = result || [subcontractor application:application continueUserActivity:userActivity restorationHandler:handler]; } return result; } #pragma mark - BackgroundSession - (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)(void))completionHandler { SEL selector = @selector(application:handleEventsForBackgroundURLSession:completionHandler:); NSArray> *subcontractorsArray = [self getSubcontractorsImplementingSelector:selector]; __block BOOL delegatingCompleted = NO; __block int delegatesCompleted = 0; __block unsigned long allDelegates = subcontractorsArray.count; __block void (^completionHandlerCaller)(void) = ^ { if (delegatesCompleted && delegatingCompleted == allDelegates) { completionHandler(); } }; for (id subcontractor in subcontractorsArray) { [subcontractor application:application handleEventsForBackgroundURLSession:identifier completionHandler:^(){ @synchronized (self) { delegatesCompleted += 1; completionHandlerCaller(); } }]; } @synchronized (self) { delegatingCompleted = YES; completionHandlerCaller(); } } #pragma mark - Notifications - (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)token { SEL selector = @selector(application:didRegisterForRemoteNotificationsWithDeviceToken:); NSArray> *subcontractorsArray = [self getSubcontractorsImplementingSelector:selector]; for (id subcontractor in subcontractorsArray) { [subcontractor application:application didRegisterForRemoteNotificationsWithDeviceToken:token]; } } - (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)err { SEL selector = @selector(application:didFailToRegisterForRemoteNotificationsWithError:); NSArray> *subcontractorsArray = [self getSubcontractorsImplementingSelector:selector]; for(id subcontractor in subcontractorsArray) { [subcontractor application:application didFailToRegisterForRemoteNotificationsWithError:err]; } } - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler { SEL selector = @selector(application:didReceiveRemoteNotification:fetchCompletionHandler:); NSArray> *subcontractorsArray = [self getSubcontractorsImplementingSelector:selector]; __block NSUInteger subcontractorsLeft = [subcontractorsArray count]; __block UIBackgroundFetchResult fetchResult = UIBackgroundFetchResultNoData; __block NSObject *lock = [NSObject new]; void (^handler)(UIBackgroundFetchResult) = ^(UIBackgroundFetchResult result) { @synchronized (lock) { if (result == UIBackgroundFetchResultFailed) { fetchResult = UIBackgroundFetchResultFailed; } else if (fetchResult != UIBackgroundFetchResultFailed && result == UIBackgroundFetchResultNewData) { fetchResult = UIBackgroundFetchResultNewData; } subcontractorsLeft--; if (subcontractorsLeft == 0) { completionHandler(fetchResult); } } }; if (subcontractorsLeft == 0) { completionHandler(fetchResult); } else { for (id subcontractor in subcontractorsArray) { [subcontractor application:application didReceiveRemoteNotification:userInfo fetchCompletionHandler:handler]; } } } #pragma mark - Subcontractors - (void)ensureSubcontractorsAreInitializedAndSorted { dispatch_once(&onceToken, ^{ subcontractors = [[NSMutableArray alloc] init]; subcontractorsForSelector = [NSMutableDictionary new]; NSArray * singletonModules = [[EXModuleRegistryProvider singletonModules] allObjects]; for (EXSingletonModule *singletonModule in singletonModules) { if ([singletonModule conformsToProtocol:@protocol(UIApplicationDelegate)]) { [subcontractors addObject:(id)singletonModule]; } } NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"priority" ascending:NO]; [subcontractors sortUsingDescriptors:[NSArray arrayWithObject:sortDescriptor]]; }); } - (NSArray> *)getSubcontractorsImplementingSelector:(SEL)selector { [self ensureSubcontractorsAreInitializedAndSorted]; NSString *selectorKey = NSStringFromSelector(selector); if (subcontractorsForSelector[selectorKey]) { return subcontractorsForSelector[selectorKey]; } NSMutableArray> *result = [NSMutableArray new]; for (id subcontractor in subcontractors) { if ([subcontractor respondsToSelector:selector]) { [result addObject:subcontractor]; } } subcontractorsForSelector[selectorKey] = result; return result; } #endif // !TARGET_OS_OSX @end