diff --git a/platform-darwin/Source/MPTypes.h b/platform-darwin/Source/MPTypes.h index 85237259..d6e54203 100644 --- a/platform-darwin/Source/MPTypes.h +++ b/platform-darwin/Source/MPTypes.h @@ -11,6 +11,7 @@ __BEGIN_DECLS extern NSString *const MPErrorDomain; +extern NSInteger const MPErrorHangCode; extern NSString *const MPSignedInNotification; extern NSString *const MPSignedOutNotification; diff --git a/platform-darwin/Source/MPTypes.m b/platform-darwin/Source/MPTypes.m index 228d5fd0..5324fa5a 100644 --- a/platform-darwin/Source/MPTypes.m +++ b/platform-darwin/Source/MPTypes.m @@ -9,6 +9,7 @@ #import "MPTypes.h" NSString *const MPErrorDomain = @"MPErrorDomain"; +NSInteger const MPErrorHangCode = 1; NSString *const MPSignedInNotification = @"MPSignedInNotification"; NSString *const MPSignedOutNotification = @"MPSignedOutNotification"; diff --git a/platform-darwin/Source/iOS/MPUsersViewController.m b/platform-darwin/Source/iOS/MPUsersViewController.m index b359add4..ac3d2a5b 100644 --- a/platform-darwin/Source/iOS/MPUsersViewController.m +++ b/platform-darwin/Source/iOS/MPUsersViewController.m @@ -273,6 +273,9 @@ typedef NS_ENUM( NSUInteger, MPActiveUserState ) { // This isn't really in UITextFieldDelegate. We fake it from UITextFieldTextDidChangeNotification. - (void)textFieldEditingChanged:(UITextField *)textField { + if ([[textField.text lowercaseString] isEqualToString:@"hangtest"]) + [NSThread sleepForTimeInterval:10]; + if (textField == self.entryField) { switch (self.activeUserState) { case MPActiveUserStateNone: diff --git a/platform-darwin/Source/iOS/MPiOSAppDelegate.m b/platform-darwin/Source/iOS/MPiOSAppDelegate.m index 67292953..c8b85f4a 100644 --- a/platform-darwin/Source/iOS/MPiOSAppDelegate.m +++ b/platform-darwin/Source/iOS/MPiOSAppDelegate.m @@ -21,6 +21,8 @@ #import "MPAppDelegate_Store.h" #import "MPStoreViewController.h" +#define MP_HANG_TIME_MAIN 3 // s + @interface MPiOSAppDelegate() @property(nonatomic, strong) UIDocumentInteractionController *interactionController; @@ -73,6 +75,8 @@ [Crashlytics sharedInstance].version, [PearlInfoPlist get].CFBundleName, [PearlInfoPlist get].CFBundleVersion ); } #endif + + [self installHangDetector]; } @catch (id exception) { err( @"During Analytics Setup: %@", exception ); @@ -139,6 +143,31 @@ return YES; } +- (void)installHangDetector { + + __block NSDate *latestPing = [NSDate date]; + __block __weak VoidBlock wPingOp, wPongOp; + + VoidBlock pingOp = ^{ + latestPing = [NSDate date]; + dispatch_after( dispatch_time( DISPATCH_TIME_NOW, 100 * NSEC_PER_MSEC ), dispatch_get_main_queue(), wPingOp ); + }, pongOp = ^{ + NSTimeInterval hangTime = -[latestPing timeIntervalSinceNow]; + if (hangTime > MP_HANG_TIME_MAIN) { + MPError( [NSError errorWithDomain:MPErrorDomain code:MPErrorHangCode userInfo:@{ + @"time": @(hangTime) + }], @"Timeout waiting for main thread after %fs.", hangTime ); + } + else + dbg( @"hangTime=%f", hangTime ); + + dispatch_after( dispatch_time( DISPATCH_TIME_NOW, NSEC_PER_SEC ), dispatch_get_global_queue( QOS_CLASS_BACKGROUND, 0 ), wPongOp ); + }; + + (wPingOp = pingOp)(); + (wPongOp = pongOp)(); +} + - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation {