From 6bcf603f2c7a4d604a5dc13f9ef5cc243c009ae3 Mon Sep 17 00:00:00 2001 From: reuk Date: Fri, 5 Mar 2021 11:26:22 +0000 Subject: [PATCH] AppDelegate: Ensure correct lifetime of static objects Arranges declarations of objects with static storage duration to ensure correct lifetimes. --- .../native/juce_mac_MessageManager.mm | 442 +++++++++--------- 1 file changed, 222 insertions(+), 220 deletions(-) diff --git a/modules/juce_events/native/juce_mac_MessageManager.mm b/modules/juce_events/native/juce_mac_MessageManager.mm index 1cc0b9f8e1..7a07188689 100644 --- a/modules/juce_events/native/juce_mac_MessageManager.mm +++ b/modules/juce_events/native/juce_mac_MessageManager.mm @@ -33,300 +33,302 @@ using MenuTrackingChangedCallback = void (*)(bool); MenuTrackingChangedCallback menuTrackingChangedCallback = nullptr; //============================================================================== -struct AppDelegate +struct AppDelegateClass : public ObjCClass { -public: - AppDelegate() + AppDelegateClass() : ObjCClass ("JUCEAppDelegate_") { - static AppDelegateClass cls; - delegate = [cls.createInstance() init]; + addMethod (@selector (applicationWillFinishLaunching:), applicationWillFinishLaunching, "v@:@"); + addMethod (@selector (applicationShouldTerminate:), applicationShouldTerminate, "I@:@"); + addMethod (@selector (applicationWillTerminate:), applicationWillTerminate, "v@:@"); + addMethod (@selector (application:openFile:), application_openFile, "c@:@@"); + addMethod (@selector (application:openFiles:), application_openFiles, "v@:@@"); + addMethod (@selector (applicationDidBecomeActive:), applicationDidBecomeActive, "v@:@"); + addMethod (@selector (applicationDidResignActive:), applicationDidResignActive, "v@:@"); + addMethod (@selector (applicationWillUnhide:), applicationWillUnhide, "v@:@"); - NSNotificationCenter* center = [NSNotificationCenter defaultCenter]; + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") + addMethod (@selector (getUrl:withReplyEvent:), getUrl_withReplyEvent, "v@:@@"); + addMethod (@selector (broadcastMessageCallback:), broadcastMessageCallback, "v@:@"); + addMethod (@selector (mainMenuTrackingBegan:), mainMenuTrackingBegan, "v@:@"); + addMethod (@selector (mainMenuTrackingEnded:), mainMenuTrackingEnded, "v@:@"); + addMethod (@selector (dummyMethod), dummyMethod, "v@:"); + JUCE_END_IGNORE_WARNINGS_GCC_LIKE + + #if JUCE_PUSH_NOTIFICATIONS + //============================================================================== + addIvar*> ("pushNotificationsDelegate"); + + addMethod (@selector (applicationDidFinishLaunching:), applicationDidFinishLaunching, "v@:@"); JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") - [center addObserver: delegate selector: @selector (mainMenuTrackingBegan:) - name: NSMenuDidBeginTrackingNotification object: nil]; - [center addObserver: delegate selector: @selector (mainMenuTrackingEnded:) - name: NSMenuDidEndTrackingNotification object: nil]; + addMethod (@selector (setPushNotificationsDelegate:), setPushNotificationsDelegate, "v@:@"); JUCE_END_IGNORE_WARNINGS_GCC_LIKE - if (JUCEApplicationBase::isStandaloneApp()) - { - [NSApp setDelegate: delegate]; + addMethod (@selector (application:didRegisterForRemoteNotificationsWithDeviceToken:), registeredForRemoteNotifications, "v@:@@"); + addMethod (@selector (application:didFailToRegisterForRemoteNotificationsWithError:), failedToRegisterForRemoteNotifications, "v@:@@"); + addMethod (@selector (application:didReceiveRemoteNotification:), didReceiveRemoteNotification, "v@:@@"); + #endif - JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") - [[NSDistributedNotificationCenter defaultCenter] addObserver: delegate - selector: @selector (broadcastMessageCallback:) - name: getBroadcastEventName() - object: nil - suspensionBehavior: NSNotificationSuspensionBehaviorDeliverImmediately]; - JUCE_END_IGNORE_WARNINGS_GCC_LIKE - } - else - { - [center addObserver: delegate selector: @selector (applicationDidResignActive:) - name: NSApplicationDidResignActiveNotification object: NSApp]; + registerClass(); + } - [center addObserver: delegate selector: @selector (applicationDidBecomeActive:) - name: NSApplicationDidBecomeActiveNotification object: NSApp]; +private: + static void applicationWillFinishLaunching (id self, SEL, NSNotification*) + { + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") + [[NSAppleEventManager sharedAppleEventManager] setEventHandler: self + andSelector: @selector (getUrl:withReplyEvent:) + forEventClass: kInternetEventClass + andEventID: kAEGetURL]; + JUCE_END_IGNORE_WARNINGS_GCC_LIKE + } - [center addObserver: delegate selector: @selector (applicationWillUnhide:) - name: NSApplicationWillUnhideNotification object: NSApp]; + #if JUCE_PUSH_NOTIFICATIONS + static void applicationDidFinishLaunching (id self, SEL, NSNotification* notification) + { + if (notification.userInfo != nil) + { + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") + // NSUserNotification is deprecated from macOS 11, but there doesn't seem to be a + // replacement for NSApplicationLaunchUserNotificationKey returning a non-deprecated type + NSUserNotification* userNotification = notification.userInfo[NSApplicationLaunchUserNotificationKey]; + JUCE_END_IGNORE_WARNINGS_GCC_LIKE + + if (userNotification != nil && userNotification.userInfo != nil) + didReceiveRemoteNotification (self, nil, [NSApplication sharedApplication], userNotification.userInfo); } } + #endif - ~AppDelegate() + static NSApplicationTerminateReply applicationShouldTerminate (id /*self*/, SEL, NSApplication*) { - [[NSRunLoop currentRunLoop] cancelPerformSelectorsWithTarget: delegate]; - [[NSNotificationCenter defaultCenter] removeObserver: delegate]; - - if (JUCEApplicationBase::isStandaloneApp()) + if (auto* app = JUCEApplicationBase::getInstance()) { - [NSApp setDelegate: nil]; + app->systemRequestedQuit(); - [[NSDistributedNotificationCenter defaultCenter] removeObserver: delegate - name: getBroadcastEventName() - object: nil]; + if (! MessageManager::getInstance()->hasStopMessageBeenSent()) + return NSTerminateCancel; } - [delegate release]; + return NSTerminateNow; } - static NSString* getBroadcastEventName() + static void applicationWillTerminate (id /*self*/, SEL, NSNotification*) { - return juceStringToNS ("juce_" + String::toHexString (File::getSpecialLocation (File::currentExecutableFile).hashCode64())); + JUCEApplicationBase::appWillTerminateByForce(); } - MessageQueue messageQueue; - id delegate; - -private: - //============================================================================== - struct AppDelegateClass : public ObjCClass + static BOOL application_openFile (id /*self*/, SEL, NSApplication*, NSString* filename) { - AppDelegateClass() : ObjCClass ("JUCEAppDelegate_") + if (auto* app = JUCEApplicationBase::getInstance()) { - addMethod (@selector (applicationWillFinishLaunching:), applicationWillFinishLaunching, "v@:@"); - addMethod (@selector (applicationShouldTerminate:), applicationShouldTerminate, "I@:@"); - addMethod (@selector (applicationWillTerminate:), applicationWillTerminate, "v@:@"); - addMethod (@selector (application:openFile:), application_openFile, "c@:@@"); - addMethod (@selector (application:openFiles:), application_openFiles, "v@:@@"); - addMethod (@selector (applicationDidBecomeActive:), applicationDidBecomeActive, "v@:@"); - addMethod (@selector (applicationDidResignActive:), applicationDidResignActive, "v@:@"); - addMethod (@selector (applicationWillUnhide:), applicationWillUnhide, "v@:@"); + app->anotherInstanceStarted (quotedIfContainsSpaces (filename)); + return YES; + } - JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") - addMethod (@selector (getUrl:withReplyEvent:), getUrl_withReplyEvent, "v@:@@"); - addMethod (@selector (broadcastMessageCallback:), broadcastMessageCallback, "v@:@"); - addMethod (@selector (mainMenuTrackingBegan:), mainMenuTrackingBegan, "v@:@"); - addMethod (@selector (mainMenuTrackingEnded:), mainMenuTrackingEnded, "v@:@"); - addMethod (@selector (dummyMethod), dummyMethod, "v@:"); - JUCE_END_IGNORE_WARNINGS_GCC_LIKE + return NO; + } - #if JUCE_PUSH_NOTIFICATIONS - //============================================================================== - addIvar*> ("pushNotificationsDelegate"); + static void application_openFiles (id /*self*/, SEL, NSApplication*, NSArray* filenames) + { + if (auto* app = JUCEApplicationBase::getInstance()) + { + StringArray files; - addMethod (@selector (applicationDidFinishLaunching:), applicationDidFinishLaunching, "v@:@"); + for (NSString* f in filenames) + files.add (quotedIfContainsSpaces (f)); - JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") - addMethod (@selector (setPushNotificationsDelegate:), setPushNotificationsDelegate, "v@:@"); - JUCE_END_IGNORE_WARNINGS_GCC_LIKE + if (files.size() > 0) + app->anotherInstanceStarted (files.joinIntoString (" ")); + } + } - addMethod (@selector (application:didRegisterForRemoteNotificationsWithDeviceToken:), registeredForRemoteNotifications, "v@:@@"); - addMethod (@selector (application:didFailToRegisterForRemoteNotificationsWithError:), failedToRegisterForRemoteNotifications, "v@:@@"); - addMethod (@selector (application:didReceiveRemoteNotification:), didReceiveRemoteNotification, "v@:@@"); - #endif + static void applicationDidBecomeActive (id /*self*/, SEL, NSNotification*) { focusChanged(); } + static void applicationDidResignActive (id /*self*/, SEL, NSNotification*) { focusChanged(); } + static void applicationWillUnhide (id /*self*/, SEL, NSNotification*) { focusChanged(); } - registerClass(); - } + static void broadcastMessageCallback (id /*self*/, SEL, NSNotification* n) + { + NSDictionary* dict = (NSDictionary*) [n userInfo]; + auto messageString = nsStringToJuce ((NSString*) [dict valueForKey: nsStringLiteral ("message")]); + MessageManager::getInstance()->deliverBroadcastMessage (messageString); + } - private: - static void applicationWillFinishLaunching (id self, SEL, NSNotification*) - { - JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") - [[NSAppleEventManager sharedAppleEventManager] setEventHandler: self - andSelector: @selector (getUrl:withReplyEvent:) - forEventClass: kInternetEventClass - andEventID: kAEGetURL]; - JUCE_END_IGNORE_WARNINGS_GCC_LIKE - } + static void mainMenuTrackingBegan (id /*self*/, SEL, NSNotification*) + { + if (menuTrackingChangedCallback != nullptr) + (*menuTrackingChangedCallback) (true); + } - #if JUCE_PUSH_NOTIFICATIONS - static void applicationDidFinishLaunching (id self, SEL, NSNotification* notification) - { - if (notification.userInfo != nil) - { - JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") - // NSUserNotification is deprecated from macOS 11, but there doesn't seem to be a - // replacement for NSApplicationLaunchUserNotificationKey returning a non-deprecated type - NSUserNotification* userNotification = notification.userInfo[NSApplicationLaunchUserNotificationKey]; - JUCE_END_IGNORE_WARNINGS_GCC_LIKE - - if (userNotification != nil && userNotification.userInfo != nil) - didReceiveRemoteNotification (self, nil, [NSApplication sharedApplication], userNotification.userInfo); - } - } - #endif + static void mainMenuTrackingEnded (id /*self*/, SEL, NSNotification*) + { + if (menuTrackingChangedCallback != nullptr) + (*menuTrackingChangedCallback) (false); + } - static NSApplicationTerminateReply applicationShouldTerminate (id /*self*/, SEL, NSApplication*) - { - if (auto* app = JUCEApplicationBase::getInstance()) - { - app->systemRequestedQuit(); + static void dummyMethod (id /*self*/, SEL) {} // (used as a way of running a dummy thread) - if (! MessageManager::getInstance()->hasStopMessageBeenSent()) - return NSTerminateCancel; - } + static void focusChanged() + { + if (appFocusChangeCallback != nullptr) + (*appFocusChangeCallback)(); + } - return NSTerminateNow; - } + static void getUrl_withReplyEvent (id /*self*/, SEL, NSAppleEventDescriptor* event, NSAppleEventDescriptor*) + { + if (auto* app = JUCEApplicationBase::getInstance()) + app->anotherInstanceStarted (quotedIfContainsSpaces ([[event paramDescriptorForKeyword: keyDirectObject] stringValue])); + } - static void applicationWillTerminate (id /*self*/, SEL, NSNotification*) - { - JUCEApplicationBase::appWillTerminateByForce(); - } + static String quotedIfContainsSpaces (NSString* file) + { + String s (nsStringToJuce (file)); + s = s.unquoted().replace ("\"", "\\\""); - static BOOL application_openFile (id /*self*/, SEL, NSApplication*, NSString* filename) - { - if (auto* app = JUCEApplicationBase::getInstance()) - { - app->anotherInstanceStarted (quotedIfContainsSpaces (filename)); - return YES; - } + if (s.containsChar (' ')) + s = s.quoted(); - return NO; - } + return s; + } - static void application_openFiles (id /*self*/, SEL, NSApplication*, NSArray* filenames) - { - if (auto* app = JUCEApplicationBase::getInstance()) - { - StringArray files; + #if JUCE_PUSH_NOTIFICATIONS + //============================================================================== + static void setPushNotificationsDelegate (id self, SEL, NSObject* delegate) + { + object_setInstanceVariable (self, "pushNotificationsDelegate", delegate); + } - for (NSString* f in filenames) - files.add (quotedIfContainsSpaces (f)); + static NSObject* getPushNotificationsDelegate (id self) + { + return getIvar*> (self, "pushNotificationsDelegate"); + } - if (files.size() > 0) - app->anotherInstanceStarted (files.joinIntoString (" ")); - } - } + static void registeredForRemoteNotifications (id self, SEL, NSApplication* application, NSData* deviceToken) + { + auto* delegate = getPushNotificationsDelegate (self); - static void applicationDidBecomeActive (id /*self*/, SEL, NSNotification*) { focusChanged(); } - static void applicationDidResignActive (id /*self*/, SEL, NSNotification*) { focusChanged(); } - static void applicationWillUnhide (id /*self*/, SEL, NSNotification*) { focusChanged(); } + SEL selector = @selector (application:didRegisterForRemoteNotificationsWithDeviceToken:); - static void broadcastMessageCallback (id /*self*/, SEL, NSNotification* n) + if (delegate != nil && [delegate respondsToSelector: selector]) { - NSDictionary* dict = (NSDictionary*) [n userInfo]; - auto messageString = nsStringToJuce ((NSString*) [dict valueForKey: nsStringLiteral ("message")]); - MessageManager::getInstance()->deliverBroadcastMessage (messageString); - } + NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [delegate methodSignatureForSelector: selector]]; + [invocation setSelector: selector]; + [invocation setTarget: delegate]; + [invocation setArgument: &application atIndex:2]; + [invocation setArgument: &deviceToken atIndex:3]; - static void mainMenuTrackingBegan (id /*self*/, SEL, NSNotification*) - { - if (menuTrackingChangedCallback != nullptr) - (*menuTrackingChangedCallback) (true); + [invocation invoke]; } + } - static void mainMenuTrackingEnded (id /*self*/, SEL, NSNotification*) - { - if (menuTrackingChangedCallback != nullptr) - (*menuTrackingChangedCallback) (false); - } + static void failedToRegisterForRemoteNotifications (id self, SEL, NSApplication* application, NSError* error) + { + auto* delegate = getPushNotificationsDelegate (self); - static void dummyMethod (id /*self*/, SEL) {} // (used as a way of running a dummy thread) + SEL selector = @selector (application:didFailToRegisterForRemoteNotificationsWithError:); - static void focusChanged() + if (delegate != nil && [delegate respondsToSelector: selector]) { - if (appFocusChangeCallback != nullptr) - (*appFocusChangeCallback)(); - } + NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [delegate methodSignatureForSelector: selector]]; + [invocation setSelector: selector]; + [invocation setTarget: delegate]; + [invocation setArgument: &application atIndex:2]; + [invocation setArgument: &error atIndex:3]; - static void getUrl_withReplyEvent (id /*self*/, SEL, NSAppleEventDescriptor* event, NSAppleEventDescriptor*) - { - if (auto* app = JUCEApplicationBase::getInstance()) - app->anotherInstanceStarted (quotedIfContainsSpaces ([[event paramDescriptorForKeyword: keyDirectObject] stringValue])); + [invocation invoke]; } + } - static String quotedIfContainsSpaces (NSString* file) - { - String s (nsStringToJuce (file)); - s = s.unquoted().replace ("\"", "\\\""); + static void didReceiveRemoteNotification (id self, SEL, NSApplication* application, NSDictionary* userInfo) + { + auto* delegate = getPushNotificationsDelegate (self); - if (s.containsChar (' ')) - s = s.quoted(); + SEL selector = @selector (application:didReceiveRemoteNotification:); - return s; - } - - #if JUCE_PUSH_NOTIFICATIONS - //============================================================================== - static void setPushNotificationsDelegate (id self, SEL, NSObject* delegate) + if (delegate != nil && [delegate respondsToSelector: selector]) { - object_setInstanceVariable (self, "pushNotificationsDelegate", delegate); - } + NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [delegate methodSignatureForSelector: selector]]; + [invocation setSelector: selector]; + [invocation setTarget: delegate]; + [invocation setArgument: &application atIndex:2]; + [invocation setArgument: &userInfo atIndex:3]; - static NSObject* getPushNotificationsDelegate (id self) - { - return getIvar*> (self, "pushNotificationsDelegate"); + [invocation invoke]; } + } + #endif +}; - static void registeredForRemoteNotifications (id self, SEL, NSApplication* application, NSData* deviceToken) - { - auto* delegate = getPushNotificationsDelegate (self); +// This is declared at file scope, so that it's guaranteed to be +// constructed before and destructed after `appDelegate` (below) +static AppDelegateClass appDelegateClass; - SEL selector = @selector (application:didRegisterForRemoteNotificationsWithDeviceToken:); +//============================================================================== +struct AppDelegate +{ +public: + AppDelegate() + { + delegate = [appDelegateClass.createInstance() init]; - if (delegate != nil && [delegate respondsToSelector: selector]) - { - NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [delegate methodSignatureForSelector: selector]]; - [invocation setSelector: selector]; - [invocation setTarget: delegate]; - [invocation setArgument: &application atIndex:2]; - [invocation setArgument: &deviceToken atIndex:3]; + NSNotificationCenter* center = [NSNotificationCenter defaultCenter]; - [invocation invoke]; - } - } + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") + [center addObserver: delegate selector: @selector (mainMenuTrackingBegan:) + name: NSMenuDidBeginTrackingNotification object: nil]; + [center addObserver: delegate selector: @selector (mainMenuTrackingEnded:) + name: NSMenuDidEndTrackingNotification object: nil]; + JUCE_END_IGNORE_WARNINGS_GCC_LIKE - static void failedToRegisterForRemoteNotifications (id self, SEL, NSApplication* application, NSError* error) + if (JUCEApplicationBase::isStandaloneApp()) { - auto* delegate = getPushNotificationsDelegate (self); + [NSApp setDelegate: delegate]; - SEL selector = @selector (application:didFailToRegisterForRemoteNotificationsWithError:); + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") + [[NSDistributedNotificationCenter defaultCenter] addObserver: delegate + selector: @selector (broadcastMessageCallback:) + name: getBroadcastEventName() + object: nil + suspensionBehavior: NSNotificationSuspensionBehaviorDeliverImmediately]; + JUCE_END_IGNORE_WARNINGS_GCC_LIKE + } + else + { + [center addObserver: delegate selector: @selector (applicationDidResignActive:) + name: NSApplicationDidResignActiveNotification object: NSApp]; - if (delegate != nil && [delegate respondsToSelector: selector]) - { - NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [delegate methodSignatureForSelector: selector]]; - [invocation setSelector: selector]; - [invocation setTarget: delegate]; - [invocation setArgument: &application atIndex:2]; - [invocation setArgument: &error atIndex:3]; + [center addObserver: delegate selector: @selector (applicationDidBecomeActive:) + name: NSApplicationDidBecomeActiveNotification object: NSApp]; - [invocation invoke]; - } + [center addObserver: delegate selector: @selector (applicationWillUnhide:) + name: NSApplicationWillUnhideNotification object: NSApp]; } + } - static void didReceiveRemoteNotification (id self, SEL, NSApplication* application, NSDictionary* userInfo) + ~AppDelegate() + { + [[NSRunLoop currentRunLoop] cancelPerformSelectorsWithTarget: delegate]; + [[NSNotificationCenter defaultCenter] removeObserver: delegate]; + + if (JUCEApplicationBase::isStandaloneApp()) { - auto* delegate = getPushNotificationsDelegate (self); + [NSApp setDelegate: nil]; - SEL selector = @selector (application:didReceiveRemoteNotification:); + [[NSDistributedNotificationCenter defaultCenter] removeObserver: delegate + name: getBroadcastEventName() + object: nil]; + } - if (delegate != nil && [delegate respondsToSelector: selector]) - { - NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [delegate methodSignatureForSelector: selector]]; - [invocation setSelector: selector]; - [invocation setTarget: delegate]; - [invocation setArgument: &application atIndex:2]; - [invocation setArgument: &userInfo atIndex:3]; + [delegate release]; + } - [invocation invoke]; - } - } - #endif - }; + static NSString* getBroadcastEventName() + { + return juceStringToNS ("juce_" + String::toHexString (File::getSpecialLocation (File::currentExecutableFile).hashCode64())); + } + + MessageQueue messageQueue; + id delegate; }; //==============================================================================