OSX/MACOS: Make [CocoaView get] honour the +0 "get" convention

+[CocoaView get] is a cached-singleton accessor.  By Cocoa
convention a method whose name does not start with new / alloc /
copy / mutableCopy returns a +0 (unretained) reference; the
singleton is kept alive by the storage it is cached in.

The existing implementation violated that convention on the
first-time path: it did [CocoaView new] (+1) and handed the
pointer to nsview_set_ptr, which was a raw `g_instance = p;` -
no retain.  The caller then received a +1 return on the first
call but +0 on every call thereafter.  Under ARC the strong
static would retain on store so it was self-consistent, but
under MRR the first-ever caller had an unbalanced +1 the code
never handled - a small process-lifetime leak.  A prior commit
(e9279a8) leaned into the +0 assumption by adding RARCH_RETAIN
wrappers in ui_cocoa.m, which silently turned that +1 into a
+2 leak whenever -setViewType: was called for a singleton-
backed view type on its first use.

Two changes, both behind the existing RARCH_* macros so ARC
builds see no-ops:

- +[CocoaView get]: autorelease the freshly-allocated instance
  before storing.  The returned pointer is now +0 on every path,
  matching the get naming convention.

- nsview_set_ptr: take a proper strong reference for the
  g_instance slot (retain new, release old).  g_instance is
  now the single owner of the singleton's +1, matching how the
  rest of the codebase already treats it (never nil-ed, never
  explicitly released - so the retain lives for the process
  lifetime, which is the intent).

Adds #include <defines/cocoa_defines.h> to cocoa_common.m for
RARCH_RETAIN / RARCH_RELEASE / RARCH_AUTORELEASE.  The include
path is already on every Apple build config (already used by
ui_cocoa.m, dispserv_apple.m, coreaudio.c).

No behavioural change under ARC.  On MRR, plugs the first-call
+1 leak and cancels the +2 over-retain that e9279a8's RARCH_RETAIN
wrappers would otherwise produce.
This commit is contained in:
U-DESKTOP-SPFP6AQ\twistedtechre committed 2026-04-21 19:01:24 +02:00
1 parent e0b7b9b216
commit 0062608a13
1 file changed
+31 -2
+31 -2
View File
@@ -19,6 +19,7 @@
#include <compat/apple_compat.h>
#include <string/stdstring.h>
#include <defines/cocoa_defines.h>
#include "cocoa_common.h"
#include "apple_platform.h"
@@ -206,7 +207,17 @@ void rarch_stop_draw_observer(void)
CocoaView *view = (BRIDGE CocoaView*)nsview_get_ptr();
if (!view)
{
view = [CocoaView new];
/* +new returns +1 owned by the caller. Autorelease before
* handing to nsview_set_ptr, which takes its own retain for
* the process-lifetime g_instance slot. Net result: the
* returned pointer obeys the Cocoa +0 "get" convention on
* both the first call (where we allocate) and every
* subsequent call (where we just read g_instance) - so
* callers do not have to guess the retain count. Under ARC
* RARCH_AUTORELEASE is a no-op; the strong local's end-of-
* scope release balances +new's +1 after g_instance's
* storeStrong has taken its own retain. */
view = RARCH_AUTORELEASE([CocoaView new]);
nsview_set_ptr(view);
#if defined(IOS)
view.displayLink = [CADisplayLink displayLinkWithTarget:view selector:@selector(step:)];
@@ -936,7 +947,25 @@ void *nsview_get_ptr(void)
return (BRIDGE void *)g_instance;
}
void nsview_set_ptr(CocoaView *p) { g_instance = p; }
void nsview_set_ptr(CocoaView *p)
{
/* g_instance is the process-lifetime strong reference to the
* CocoaView singleton. Under MRR we must explicitly retain the
* new value and release the old one so g_instance owns a
* balanced +1 across reassignments. In practice there is only
* one caller (+[CocoaView get] on its first-time path), but the
* invariant matters: callers treat [CocoaView get] as a +0 "get"
* accessor, and that only holds if g_instance is the one keeping
* the view alive. Under ARC RARCH_RETAIN and RARCH_RELEASE are
* no-ops; the static __strong pointer does retain/release via
* objc_storeStrong when assigned. */
if (g_instance != p)
{
RARCH_RETAIN(p);
RARCH_RELEASE(g_instance);
g_instance = p;
}
}
CocoaView *cocoaview_get(void)
{