diff --git a/src/UIKit/UITraitChangeObservable.cs b/src/UIKit/UITraitChangeObservable.cs index 156e35ccc90..ec20e57aca9 100644 --- a/src/UIKit/UITraitChangeObservable.cs +++ b/src/UIKit/UITraitChangeObservable.cs @@ -26,6 +26,26 @@ public static Class [] ToClasses (params Type [] traits) return Class.FromTypes (traits); } + // Register the observing object with the toggle-ref GC bridge before we hand a + // reference to it to native code. Without this, the managed peer's toggle-ref status + // defaults to whatever xamarin_gc_toggleref_callback infers from -retainCount, which + // only reflects normal ObjC retains: it can't see that _UITraitChangeRegistry now also + // holds a reference to this object internally (observer registries are conventionally + // non-retaining, to avoid retain cycles with their observers). If -retainCount is 1 at + // the next GC, the bridge downgrades the peer to a weak GC handle and it can be + // collected while _UITraitChangeRegistry still references it, corrupting the shared + // registry (a crash then tends to surface later, in unrelated code that next touches + // the registry, rather than here). MarkDirty is idempotent and mirrors the pattern + // already used by UIControl.AddTarget, UIGestureRecognizer, and + // NSNotificationCenter.AddObserver for the same reason. + private static void MarkDirtyForTraitRegistration (IUITraitChangeObservable observable) + { + // NSObject.MarkDirty() is 'protected'; the (bool) overload is 'internal' and can be + // called from anywhere in this assembly, which is what we need from a static method + // on an unrelated interface. + (observable as NSObject)?.MarkDirty (false); + } + /// /// Registers a callback handler that will be executed when one of the specified traits changes. /// @@ -39,6 +59,7 @@ public IUITraitChangeRegistration RegisterForTraitChanges (Type [] traits, Actio internal static IUITraitChangeRegistration _RegisterForTraitChanges (IUITraitChangeObservable This, Type [] traits, Action handler) { + MarkDirtyForTraitRegistration (This); return _RegisterForTraitChanges (This, ToClasses (traits), handler); } @@ -56,6 +77,7 @@ public IUITraitChangeRegistration RegisterForTraitChanges (Action handler, params Type [] traits) { // Add an override with 'params', unfortunately this means reordering the parameters. + MarkDirtyForTraitRegistration (This); return _RegisterForTraitChanges (This, ToClasses (traits), handler); } @@ -74,6 +96,7 @@ public IUITraitChangeRegistration RegisterForTraitChanges (Action (IUITraitChangeObservable This, Action handler) where T : IUITraitDefinition { + MarkDirtyForTraitRegistration (This); return _RegisterForTraitChanges (This, ToClasses (typeof (T)), handler); } @@ -95,6 +118,7 @@ internal static IUITraitChangeRegistration _RegisterForTraitChanges (IUI where T1 : IUITraitDefinition where T2 : IUITraitDefinition { + MarkDirtyForTraitRegistration (This); return _RegisterForTraitChanges (This, ToClasses (typeof (T1), typeof (T2)), handler); } @@ -119,6 +143,7 @@ internal static IUITraitChangeRegistration _RegisterForTraitChanges where T2 : IUITraitDefinition where T3 : IUITraitDefinition { + MarkDirtyForTraitRegistration (This); return _RegisterForTraitChanges (This, ToClasses (typeof (T1), typeof (T2), typeof (T3)), handler); } @@ -146,6 +171,7 @@ internal static IUITraitChangeRegistration _RegisterForTraitChanges