Skip to content

Commit

Permalink
Fix Swift crashes due to AppDelegate proxy cast issues. Instead, use …
Browse files Browse the repository at this point in the history
…ObjC runtime to dynamically subclass the delegate class with own functionality. Closes #165

Add Swift example project for testing.
  • Loading branch information
LeoNatan committed Jul 30, 2017
1 parent 7653381 commit 877792f
Show file tree
Hide file tree
Showing 13 changed files with 829 additions and 52 deletions.
12 changes: 11 additions & 1 deletion detox/ios/Detox.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -479,7 +479,7 @@
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 0830;
LastUpgradeCheck = 0830;
LastUpgradeCheck = 0900;
ORGANIZATIONNAME = Wix;
TargetAttributes = {
3928EFA41E47404900C19B6E = {
Expand Down Expand Up @@ -729,15 +729,20 @@
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_SUSPICIOUS_MOVES = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
Expand Down Expand Up @@ -784,15 +789,20 @@
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_SUSPICIOUS_MOVES = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0830"
LastUpgradeVersion = "0900"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
Expand All @@ -26,6 +26,7 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
shouldUseLaunchSchemeArgsEnv = "YES"
codeCoverageEnabled = "YES">
<Testables>
Expand Down Expand Up @@ -56,6 +57,7 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0830"
LastUpgradeVersion = "0900"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
Expand All @@ -26,6 +26,7 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
Expand All @@ -36,6 +37,7 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
Expand Down
3 changes: 1 addition & 2 deletions detox/ios/Detox/DetoxAppDelegateProxy.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,8 @@
@import Foundation;
@import UIKit;

@interface DetoxAppDelegateProxy : NSObject
@interface DetoxAppDelegateProxy : NSObject <UIApplicationDelegate>

@property (class, nonatomic, strong, readonly) DetoxAppDelegateProxy* currentAppDelegateProxy;
@property (nonatomic, strong, readonly) id<UIApplicationDelegate> originalAppDelegate;

@end
90 changes: 44 additions & 46 deletions detox/ios/Detox/DetoxAppDelegateProxy.m
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,6 @@ @interface DetoxAppDelegateProxy () <UIApplicationDelegate>
@end

@implementation DetoxAppDelegateProxy
{
NSObject<UIApplicationDelegate>* _originalAppDelegate;
DetoxUserNotificationDispatcher* _notificationDispatcher;
}

+ (instancetype)currentAppDelegateProxy
{
Expand All @@ -36,44 +32,30 @@ + (void)load
Method m = class_getInstanceMethod([UIApplication class], @selector(setDelegate:));
void (*orig)(id, SEL, id<UIApplicationDelegate>) = (void*)method_getImplementation(m);
method_setImplementation(m, imp_implementationWithBlock(^ (id _self, id<UIApplicationDelegate> origDelegate) {
_currentAppDelegateProxy = [[DetoxAppDelegateProxy alloc] initWithOriginalAppDelegate:origDelegate];
orig(_self, @selector(setDelegate:), _currentAppDelegateProxy);
NSString* clsName = [NSString stringWithFormat:@"%@(%@)", NSStringFromClass([origDelegate class]), NSStringFromClass([DetoxAppDelegateProxy class])];
Class cls = objc_getClass(clsName.UTF8String);

if(cls == nil)
{
cls = objc_duplicateClass([DetoxAppDelegateProxy class], clsName.UTF8String, 0);
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated"
class_setSuperclass(cls, origDelegate.class);
#pragma clang diagnostic pop
}

object_setClass(origDelegate, cls);

_currentAppDelegateProxy = origDelegate;
orig(_self, @selector(setDelegate:), origDelegate);

[[NSNotificationCenter defaultCenter] addObserver:origDelegate selector:@selector(__dtx_applicationDidLaunchNotification:) name:UIApplicationDidFinishLaunchingNotification object:nil];
}));
}

- (instancetype)initWithOriginalAppDelegate:(id<UIApplicationDelegate>)originalAppDelegate
{
_originalAppDelegate = originalAppDelegate;

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_applicationDidLaunchNotification:) name:UIApplicationDidFinishLaunchingNotification object:nil];

return self;
}

- (id<UIApplicationDelegate>)originalAppDelegate
{
return _originalAppDelegate;
}

- (void)_applicationDidLaunchNotification:(NSNotification*)notification
{
[self.userNotificationDispatcher dispatchOnAppDelegate:_originalAppDelegate simulateDuringLaunch:YES];
}

- (BOOL)respondsToSelector:(SEL)aSelector
- (void)__dtx_applicationDidLaunchNotification:(NSNotification*)notification
{
return [_originalAppDelegate respondsToSelector:aSelector];
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel
{
NSMethodSignature* ms = [super methodSignatureForSelector:sel];
return ms ?: [_originalAppDelegate methodSignatureForSelector:sel];
}

- (void)forwardInvocation:(NSInvocation *)invocation
{
[invocation invokeWithTarget:_originalAppDelegate];
[self.userNotificationDispatcher dispatchOnAppDelegate:self simulateDuringLaunch:YES];
}

- (NSURL*)_userNotificationDataURL
Expand Down Expand Up @@ -125,31 +107,47 @@ - (NSDictionary*)_prepareLaunchOptions:(NSDictionary*)launchOptions userNotifica

- (DetoxUserNotificationDispatcher*)userNotificationDispatcher
{
if(_notificationDispatcher) { return _notificationDispatcher; }
DetoxUserNotificationDispatcher* rv = objc_getAssociatedObject(self, _cmd);

if([self _userNotificationDataURL]) {
_notificationDispatcher = [[DetoxUserNotificationDispatcher alloc] initWithUserNotificationDataURL:[self _userNotificationDataURL]];
if([self _userNotificationDataURL])
{
rv = [[DetoxUserNotificationDispatcher alloc] initWithUserNotificationDataURL:[self _userNotificationDataURL]];
objc_setAssociatedObject(self, _cmd, rv, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

return _notificationDispatcher;
return rv;
}

- (BOOL)application:(UIApplication *)application willFinishLaunchingWithOptions:(nullable NSDictionary<UIApplicationLaunchOptionsKey, id>*)launchOptions
{
launchOptions = [self _prepareLaunchOptions:launchOptions userNotificationDispatcher:self.userNotificationDispatcher];

return [_originalAppDelegate application:application willFinishLaunchingWithOptions:launchOptions];
BOOL rv = YES;
if([class_getSuperclass(object_getClass(self)) instancesRespondToSelector:_cmd])
{
struct objc_super super = {.receiver = self, .super_class = class_getSuperclass(object_getClass(self))};
BOOL (*super_class)(struct objc_super*, SEL, id, id) = (void*)objc_msgSendSuper;
rv = super_class(&super, _cmd, application, launchOptions);
}

return rv;
}

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(nullable NSDictionary<UIApplicationLaunchOptionsKey, id> *)launchOptions
{
launchOptions = [self _prepareLaunchOptions:launchOptions userNotificationDispatcher:self.userNotificationDispatcher];

BOOL rv = [_originalAppDelegate application:application didFinishLaunchingWithOptions:launchOptions];
BOOL rv = YES;
if([class_getSuperclass(object_getClass(self)) instancesRespondToSelector:_cmd])
{
struct objc_super super = {.receiver = self, .super_class = class_getSuperclass(object_getClass(self))};
BOOL (*super_class)(struct objc_super*, SEL, id, id) = (void*)objc_msgSendSuper;
rv = super_class(&super, _cmd, application, launchOptions);
}

if(self.userNotificationDispatcher == nil && [self _URLOverride] && [_originalAppDelegate respondsToSelector:@selector(application:openURL:options:)])
if(self.userNotificationDispatcher == nil && [self _URLOverride] && [class_getSuperclass(object_getClass(self)) instancesRespondToSelector:@selector(application:openURL:options:)])
{
[_originalAppDelegate application:[UIApplication sharedApplication] openURL:[self _URLOverride] options:launchOptions];
[self application:application openURL:[self _URLOverride] options:launchOptions];
}

return rv;
Expand Down
2 changes: 1 addition & 1 deletion detox/ios/Detox/DetoxManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ - (void) websocketDidReceiveAction:(NSString *)type withParams:(NSDictionary *)p
{
NSURL* userNotificationDataURL = [NSURL fileURLWithPath:params[@"detoxUserNotificationDataURL"]];
DetoxUserNotificationDispatcher* dispatcher = [[DetoxUserNotificationDispatcher alloc] initWithUserNotificationDataURL:userNotificationDataURL];
[dispatcher dispatchOnAppDelegate:DetoxAppDelegateProxy.currentAppDelegateProxy.originalAppDelegate simulateDuringLaunch:NO];
[dispatcher dispatchOnAppDelegate:DetoxAppDelegateProxy.currentAppDelegateProxy simulateDuringLaunch:NO];
[self.websocket sendAction:@"userNotificationDone" withParams:@{} withMessageId: messageId];
}
else if([type isEqualToString:@"openURL"])
Expand Down
Loading

0 comments on commit 877792f

Please sign in to comment.