xamarin / xamarin-macios

.NET for iOS, Mac Catalyst, macOS, and tvOS provide open-source bindings of the Apple SDKs for use with .NET managed languages such as C#
Other
2.44k stars 508 forks source link

[RFC] Provide a mechanism to handle WeakReference deduplication in the framework #11310

Open Therzok opened 3 years ago

Therzok commented 3 years ago

Steps to Reproduce

  1. Create a treeview, where a node has a reference to its children and its parent
  2. To avoid memory leaks, the back-reference to the parent has to be WeakReference
class Node
{
  WeakReference<Node> _parent;
  public Node(Node parent) {
    _parent = new WeakReference(parent);
  }
}

In a case of 1 parent with 5 children, there will be 5 WeakReference allocations for the same object. An alternative is to provide the WeakReference as a parameter, and that would complicate most of the code involved.

Proposal:

class Node
{
  WeakReference<Node> _parent;
  public Node(Node parent) {
    _parent = parent.WeakSelf();
  }
}

The existence of this API in Xamarin.Mac would allow us to have a sure way of grabbing a deduplicated reference to an NSObject. While it can be provided by our application libraries, other users of Xamarin.Mac that do not directly reference it would probably hit this.

@rolfbjarne proposed using a ConditionalWeakTable. The problem here is that we lose type safety

ConditionalWeakTable<NSObject, object> cwt = new();

public static WeakReference<T> WeakSelf<T>(this T target) where T:NSObject
{
  return (WeakReference<T>)cwt.GetValue(target, key => new WeakReference<T>(key));
}
mandel-macaque commented 3 years ago

@spouliot @rolfbjarne comments?

Therzok commented 3 years ago

Hmmm, no, the initial concern I had still exists.

You have to lose type safety, because the same object objects can be stored as either base or derived classes.

NSString x = ...;
NSObject y = x;

var wr1 = x.WeakSelf();
var wr2 = y.WeakSelf();
// type mismatch because WeakReference is not variant
Therzok commented 3 years ago

So, it would be something like, which is not nice at all.

ConditionalWeakTable<NSObject, WeakReference> cwt = new();

public static WeakReference WeakSelf(this NSObject target)
{
  return (WeakReference)cwt.GetValue(target, key => new WeakReference(key));
}
rolfbjarne commented 3 years ago
// type mismatch because WeakReference is not variant

Maybe you could implement your own variant version of WeakReference (based off GCHandle)?

Therzok commented 3 years ago

EDITED: The end implementation is not the problem here, I think. Having support for this in the framework to allow multiple different users to deduplicate all weak references of one object is really important in many cases, since allocations are going to be heavy for duplicate weakreferences, multiple weak gchandles created and just having a WeakSelf extension would make everyone's life easier.