2 using System.Collections.Generic;
4 using System.Reflection;
6 namespace Tizen.NUI.Binding
8 internal interface IMessagingCenter
10 void Send<TSender, TArgs>(TSender sender, string message, TArgs args) where TSender : class;
12 void Send<TSender>(TSender sender, string message) where TSender : class;
14 void Subscribe<TSender, TArgs>(object subscriber, string message, Action<TSender, TArgs> callback, TSender source = null) where TSender : class;
16 void Subscribe<TSender>(object subscriber, string message, Action<TSender> callback, TSender source = null) where TSender : class;
18 void Unsubscribe<TSender, TArgs>(object subscriber, string message) where TSender : class;
20 void Unsubscribe<TSender>(object subscriber, string message) where TSender : class;
23 internal class MessagingCenter : IMessagingCenter
25 public static IMessagingCenter Instance { get; } = new MessagingCenter();
27 class Sender : Tuple<string, Type, Type>
29 public Sender(string message, Type senderType, Type argType) : base(message, senderType, argType)
34 delegate bool Filter(object sender);
36 class MaybeWeakReference
38 WeakReference DelegateWeakReference { get; }
39 object DelegateStrongReference { get; }
41 readonly bool _isStrongReference;
43 public MaybeWeakReference(object subscriber, object delegateSource)
45 if (subscriber.Equals(delegateSource))
47 // The target is the subscriber; we can use a weakreference
48 DelegateWeakReference = new WeakReference(delegateSource);
49 _isStrongReference = false;
53 DelegateStrongReference = delegateSource;
54 _isStrongReference = true;
58 public object Target => _isStrongReference ? DelegateStrongReference : DelegateWeakReference.Target;
59 public bool IsAlive => _isStrongReference || DelegateWeakReference.IsAlive;
62 class Subscription : Tuple<WeakReference, MaybeWeakReference, MethodInfo, Filter>
64 public Subscription(object subscriber, object delegateSource, MethodInfo methodInfo, Filter filter)
65 : base(new WeakReference(subscriber), new MaybeWeakReference(subscriber, delegateSource), methodInfo, filter)
69 public WeakReference Subscriber => Item1;
70 MaybeWeakReference DelegateSource => Item2;
71 MethodInfo MethodInfo => Item3;
72 Filter Filter => Item4;
74 public void InvokeCallback(object sender, object args)
81 if (MethodInfo.IsStatic)
83 MethodInfo.Invoke(null, MethodInfo.GetParameters().Length == 1 ? new[] { sender } : new[] { sender, args });
87 var target = DelegateSource.Target;
94 MethodInfo.Invoke(target, MethodInfo.GetParameters().Length == 1 ? new[] { sender } : new[] { sender, args });
97 public bool CanBeRemoved()
99 return !Subscriber.IsAlive || !DelegateSource.IsAlive;
103 readonly Dictionary<Sender, List<Subscription>> _subscriptions =
104 new Dictionary<Sender, List<Subscription>>();
106 public static void Send<TSender, TArgs>(TSender sender, string message, TArgs args) where TSender : class
108 Instance.Send(sender, message, args);
111 void IMessagingCenter.Send<TSender, TArgs>(TSender sender, string message, TArgs args)
114 throw new ArgumentNullException(nameof(sender));
115 InnerSend(message, typeof(TSender), typeof(TArgs), sender, args);
118 public static void Send<TSender>(TSender sender, string message) where TSender : class
120 Instance.Send(sender, message);
123 void IMessagingCenter.Send<TSender>(TSender sender, string message)
126 throw new ArgumentNullException(nameof(sender));
127 InnerSend(message, typeof(TSender), null, sender, null);
130 public static void Subscribe<TSender, TArgs>(object subscriber, string message, Action<TSender, TArgs> callback, TSender source = null) where TSender : class
132 Instance.Subscribe(subscriber, message, callback, source);
135 void IMessagingCenter.Subscribe<TSender, TArgs>(object subscriber, string message, Action<TSender, TArgs> callback, TSender source)
137 if (subscriber == null)
138 throw new ArgumentNullException(nameof(subscriber));
139 if (callback == null)
140 throw new ArgumentNullException(nameof(callback));
142 var target = callback.Target;
144 Filter filter = sender =>
146 var send = (TSender)sender;
147 return (source == null || send == source);
150 InnerSubscribe(subscriber, message, typeof(TSender), typeof(TArgs), target, callback.GetMethodInfo(), filter);
153 public static void Subscribe<TSender>(object subscriber, string message, Action<TSender> callback, TSender source = null) where TSender : class
155 Instance.Subscribe(subscriber, message, callback, source);
158 void IMessagingCenter.Subscribe<TSender>(object subscriber, string message, Action<TSender> callback, TSender source)
160 if (subscriber == null)
161 throw new ArgumentNullException(nameof(subscriber));
162 if (callback == null)
163 throw new ArgumentNullException(nameof(callback));
165 var target = callback.Target;
167 Filter filter = sender =>
169 var send = (TSender)sender;
170 return (source == null || send == source);
173 InnerSubscribe(subscriber, message, typeof(TSender), null, target, callback.GetMethodInfo(), filter);
176 public static void Unsubscribe<TSender, TArgs>(object subscriber, string message) where TSender : class
178 Instance.Unsubscribe<TSender, TArgs>(subscriber, message);
181 void IMessagingCenter.Unsubscribe<TSender, TArgs>(object subscriber, string message)
183 InnerUnsubscribe(message, typeof(TSender), typeof(TArgs), subscriber);
186 public static void Unsubscribe<TSender>(object subscriber, string message) where TSender : class
188 Instance.Unsubscribe<TSender>(subscriber, message);
191 void IMessagingCenter.Unsubscribe<TSender>(object subscriber, string message)
193 InnerUnsubscribe(message, typeof(TSender), null, subscriber);
196 void InnerSend(string message, Type senderType, Type argType, object sender, object args)
199 throw new ArgumentNullException(nameof(message));
200 var key = new Sender(message, senderType, argType);
201 if (!_subscriptions.ContainsKey(key))
203 List<Subscription> subcriptions = _subscriptions[key];
204 if (subcriptions == null || !subcriptions.Any())
205 return; // should not be reachable
207 // ok so this code looks a bit funky but here is the gist of the problem. It is possible that in the course
208 // of executing the callbacks for this message someone will subscribe/unsubscribe from the same message in
209 // the callback. This would invalidate the enumerator. To work around this we make a copy. However if you unsubscribe
210 // from a message you can fairly reasonably expect that you will therefor not receive a call. To fix this we then
211 // check that the item we are about to send the message to actually exists in the live list.
212 List<Subscription> subscriptionsCopy = subcriptions.ToList();
213 foreach (Subscription subscription in subscriptionsCopy)
215 if (subscription.Subscriber.Target != null && subcriptions.Contains(subscription))
217 subscription.InvokeCallback(sender, args);
222 void InnerSubscribe(object subscriber, string message, Type senderType, Type argType, object target, MethodInfo methodInfo, Filter filter)
225 throw new ArgumentNullException(nameof(message));
226 var key = new Sender(message, senderType, argType);
227 var value = new Subscription(subscriber, target, methodInfo, filter);
228 if (_subscriptions.ContainsKey(key))
230 _subscriptions[key].Add(value);
234 var list = new List<Subscription> { value };
235 _subscriptions[key] = list;
239 void InnerUnsubscribe(string message, Type senderType, Type argType, object subscriber)
241 if (subscriber == null)
242 throw new ArgumentNullException(nameof(subscriber));
244 throw new ArgumentNullException(nameof(message));
246 var key = new Sender(message, senderType, argType);
247 if (!_subscriptions.ContainsKey(key))
249 _subscriptions[key].RemoveAll(sub => sub.CanBeRemoved() || sub.Subscriber.Target == subscriber);
250 if (!_subscriptions[key].Any())
251 _subscriptions.Remove(key);
254 // This is a bit gross; it only exists to support the unit tests in PageTests
255 // because the implementations of ActionSheet, Alert, and IsBusy are all very
256 // tightly coupled to the MessagingCenter singleton
257 internal static void ClearSubscribers()
259 (Instance as MessagingCenter)?._subscriptions.Clear();