262 строки
8.5 KiB
C#
262 строки
8.5 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
|
|
namespace Xamarin.Forms
|
|
{
|
|
public interface IMessagingCenter
|
|
{
|
|
void Send<TSender, TArgs>(TSender sender, string message, TArgs args) where TSender : class;
|
|
|
|
void Send<TSender>(TSender sender, string message) where TSender : class;
|
|
|
|
void Subscribe<TSender, TArgs>(object subscriber, string message, Action<TSender, TArgs> callback, TSender source = null) where TSender : class;
|
|
|
|
void Subscribe<TSender>(object subscriber, string message, Action<TSender> callback, TSender source = null) where TSender : class;
|
|
|
|
void Unsubscribe<TSender, TArgs>(object subscriber, string message) where TSender : class;
|
|
|
|
void Unsubscribe<TSender>(object subscriber, string message) where TSender : class;
|
|
}
|
|
|
|
public class MessagingCenter : IMessagingCenter
|
|
{
|
|
public static IMessagingCenter Instance { get; } = new MessagingCenter();
|
|
|
|
class Sender : Tuple<string, Type, Type>
|
|
{
|
|
public Sender(string message, Type senderType, Type argType) : base(message, senderType, argType)
|
|
{
|
|
}
|
|
}
|
|
|
|
delegate bool Filter(object sender);
|
|
|
|
class MaybeWeakReference
|
|
{
|
|
WeakReference DelegateWeakReference { get; }
|
|
object DelegateStrongReference { get; }
|
|
|
|
readonly bool _isStrongReference;
|
|
|
|
public MaybeWeakReference(object subscriber, object delegateSource)
|
|
{
|
|
if (subscriber.Equals(delegateSource))
|
|
{
|
|
// The target is the subscriber; we can use a weakreference
|
|
DelegateWeakReference = new WeakReference(delegateSource);
|
|
_isStrongReference = false;
|
|
}
|
|
else
|
|
{
|
|
DelegateStrongReference = delegateSource;
|
|
_isStrongReference = true;
|
|
}
|
|
}
|
|
|
|
public object Target => _isStrongReference ? DelegateStrongReference : DelegateWeakReference.Target;
|
|
public bool IsAlive => _isStrongReference || DelegateWeakReference.IsAlive;
|
|
}
|
|
|
|
class Subscription : Tuple<WeakReference, MaybeWeakReference, MethodInfo, Filter>
|
|
{
|
|
public Subscription(object subscriber, object delegateSource, MethodInfo methodInfo, Filter filter)
|
|
: base(new WeakReference(subscriber), new MaybeWeakReference(subscriber, delegateSource), methodInfo, filter)
|
|
{
|
|
}
|
|
|
|
public WeakReference Subscriber => Item1;
|
|
MaybeWeakReference DelegateSource => Item2;
|
|
MethodInfo MethodInfo => Item3;
|
|
Filter Filter => Item4;
|
|
|
|
public void InvokeCallback(object sender, object args)
|
|
{
|
|
if (!Filter(sender))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (MethodInfo.IsStatic)
|
|
{
|
|
MethodInfo.Invoke(null, MethodInfo.GetParameters().Length == 1 ? new[] { sender } : new[] { sender, args });
|
|
return;
|
|
}
|
|
|
|
var target = DelegateSource.Target;
|
|
|
|
if (target == null)
|
|
{
|
|
return; // Collected
|
|
}
|
|
|
|
MethodInfo.Invoke(target, MethodInfo.GetParameters().Length == 1 ? new[] { sender } : new[] { sender, args });
|
|
}
|
|
|
|
public bool CanBeRemoved()
|
|
{
|
|
return !Subscriber.IsAlive || !DelegateSource.IsAlive;
|
|
}
|
|
}
|
|
|
|
readonly Dictionary<Sender, List<Subscription>> _subscriptions =
|
|
new Dictionary<Sender, List<Subscription>>();
|
|
|
|
public static void Send<TSender, TArgs>(TSender sender, string message, TArgs args) where TSender : class
|
|
{
|
|
Instance.Send(sender, message, args);
|
|
}
|
|
|
|
void IMessagingCenter.Send<TSender, TArgs>(TSender sender, string message, TArgs args)
|
|
{
|
|
if (sender == null)
|
|
throw new ArgumentNullException(nameof(sender));
|
|
InnerSend(message, typeof(TSender), typeof(TArgs), sender, args);
|
|
}
|
|
|
|
public static void Send<TSender>(TSender sender, string message) where TSender : class
|
|
{
|
|
Instance.Send(sender, message);
|
|
}
|
|
|
|
void IMessagingCenter.Send<TSender>(TSender sender, string message)
|
|
{
|
|
if (sender == null)
|
|
throw new ArgumentNullException(nameof(sender));
|
|
InnerSend(message, typeof(TSender), null, sender, null);
|
|
}
|
|
|
|
public static void Subscribe<TSender, TArgs>(object subscriber, string message, Action<TSender, TArgs> callback, TSender source = null) where TSender : class
|
|
{
|
|
Instance.Subscribe(subscriber, message, callback, source);
|
|
}
|
|
|
|
void IMessagingCenter.Subscribe<TSender, TArgs>(object subscriber, string message, Action<TSender, TArgs> callback, TSender source)
|
|
{
|
|
if (subscriber == null)
|
|
throw new ArgumentNullException(nameof(subscriber));
|
|
if (callback == null)
|
|
throw new ArgumentNullException(nameof(callback));
|
|
|
|
var target = callback.Target;
|
|
|
|
Filter filter = sender =>
|
|
{
|
|
var send = (TSender)sender;
|
|
return (source == null || send == source);
|
|
};
|
|
|
|
InnerSubscribe(subscriber, message, typeof(TSender), typeof(TArgs), target, callback.GetMethodInfo(), filter);
|
|
}
|
|
|
|
public static void Subscribe<TSender>(object subscriber, string message, Action<TSender> callback, TSender source = null) where TSender : class
|
|
{
|
|
Instance.Subscribe(subscriber, message, callback, source);
|
|
}
|
|
|
|
void IMessagingCenter.Subscribe<TSender>(object subscriber, string message, Action<TSender> callback, TSender source)
|
|
{
|
|
if (subscriber == null)
|
|
throw new ArgumentNullException(nameof(subscriber));
|
|
if (callback == null)
|
|
throw new ArgumentNullException(nameof(callback));
|
|
|
|
var target = callback.Target;
|
|
|
|
Filter filter = sender =>
|
|
{
|
|
var send = (TSender)sender;
|
|
return (source == null || send == source);
|
|
};
|
|
|
|
InnerSubscribe(subscriber, message, typeof(TSender), null, target, callback.GetMethodInfo(), filter);
|
|
}
|
|
|
|
public static void Unsubscribe<TSender, TArgs>(object subscriber, string message) where TSender : class
|
|
{
|
|
Instance.Unsubscribe<TSender, TArgs>(subscriber, message);
|
|
}
|
|
|
|
void IMessagingCenter.Unsubscribe<TSender, TArgs>(object subscriber, string message)
|
|
{
|
|
InnerUnsubscribe(message, typeof(TSender), typeof(TArgs), subscriber);
|
|
}
|
|
|
|
public static void Unsubscribe<TSender>(object subscriber, string message) where TSender : class
|
|
{
|
|
Instance.Unsubscribe<TSender>(subscriber, message);
|
|
}
|
|
|
|
void IMessagingCenter.Unsubscribe<TSender>(object subscriber, string message)
|
|
{
|
|
InnerUnsubscribe(message, typeof(TSender), null, subscriber);
|
|
}
|
|
|
|
void InnerSend(string message, Type senderType, Type argType, object sender, object args)
|
|
{
|
|
if (message == null)
|
|
throw new ArgumentNullException(nameof(message));
|
|
var key = new Sender(message, senderType, argType);
|
|
if (!_subscriptions.ContainsKey(key))
|
|
return;
|
|
List<Subscription> subcriptions = _subscriptions[key];
|
|
if (subcriptions == null || !subcriptions.Any())
|
|
return; // should not be reachable
|
|
|
|
// ok so this code looks a bit funky but here is the gist of the problem. It is possible that in the course
|
|
// of executing the callbacks for this message someone will subscribe/unsubscribe from the same message in
|
|
// the callback. This would invalidate the enumerator. To work around this we make a copy. However if you unsubscribe
|
|
// from a message you can fairly reasonably expect that you will therefor not receive a call. To fix this we then
|
|
// check that the item we are about to send the message to actually exists in the live list.
|
|
List<Subscription> subscriptionsCopy = subcriptions.ToList();
|
|
foreach (Subscription subscription in subscriptionsCopy)
|
|
{
|
|
if (subscription.Subscriber.Target != null && subcriptions.Contains(subscription))
|
|
{
|
|
subscription.InvokeCallback(sender, args);
|
|
}
|
|
}
|
|
}
|
|
|
|
void InnerSubscribe(object subscriber, string message, Type senderType, Type argType, object target, MethodInfo methodInfo, Filter filter)
|
|
{
|
|
if (message == null)
|
|
throw new ArgumentNullException(nameof(message));
|
|
var key = new Sender(message, senderType, argType);
|
|
var value = new Subscription(subscriber, target, methodInfo, filter);
|
|
if (_subscriptions.ContainsKey(key))
|
|
{
|
|
_subscriptions[key].Add(value);
|
|
}
|
|
else
|
|
{
|
|
var list = new List<Subscription> { value };
|
|
_subscriptions[key] = list;
|
|
}
|
|
}
|
|
|
|
void InnerUnsubscribe(string message, Type senderType, Type argType, object subscriber)
|
|
{
|
|
if (subscriber == null)
|
|
throw new ArgumentNullException(nameof(subscriber));
|
|
if (message == null)
|
|
throw new ArgumentNullException(nameof(message));
|
|
|
|
var key = new Sender(message, senderType, argType);
|
|
if (!_subscriptions.ContainsKey(key))
|
|
return;
|
|
_subscriptions[key].RemoveAll(sub => sub.CanBeRemoved() || sub.Subscriber.Target == subscriber);
|
|
if (!_subscriptions[key].Any())
|
|
_subscriptions.Remove(key);
|
|
}
|
|
|
|
// This is a bit gross; it only exists to support the unit tests in PageTests
|
|
// because the implementations of ActionSheet, Alert, and IsBusy are all very
|
|
// tightly coupled to the MessagingCenter singleton
|
|
internal static void ClearSubscribers()
|
|
{
|
|
(Instance as MessagingCenter)?._subscriptions.Clear();
|
|
}
|
|
}
|
|
} |