[NUI] Use weak reference for theme changed event
authorJiyun Yang <ji.yang@samsung.com>
Fri, 20 Nov 2020 08:39:04 +0000 (17:39 +0900)
committerdongsug-song <35130733+dongsug-song@users.noreply.github.com>
Tue, 8 Dec 2020 06:23:12 +0000 (15:23 +0900)
Previous theme changed event was holding reference of the view for the theme manager's lifetime
unless the view is disposed by calling Dispose() explicitly.
Since the theme manager is a static class, so there might be a change that the view can't get free by GC.
To solve this problem, this patch apply a weak event that uses weak reference for the handler
so that the views are no longer be caught by theme manager.

Signed-off-by: Jiyun Yang <ji.yang@samsung.com>
src/Tizen.NUI/src/internal/WeakEvent.cs [new file with mode: 0644]
src/Tizen.NUI/src/public/BaseComponents/ViewBindableProperty.cs
src/Tizen.NUI/src/public/BaseComponents/ViewInternal.cs
src/Tizen.NUI/src/public/Theme/ThemeManager.cs

diff --git a/src/Tizen.NUI/src/internal/WeakEvent.cs b/src/Tizen.NUI/src/internal/WeakEvent.cs
new file mode 100644 (file)
index 0000000..de710a3
--- /dev/null
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2020 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+
+namespace Tizen.NUI
+{
+    internal class WeakEvent<T>
+    {
+        private List<WeakHandler<T>> handlers = new List<WeakHandler<T>>();
+
+        public void Add(T handler)
+        {
+            handlers.Add(new WeakHandler<T>(handler));
+        }
+
+        public void Remove(T handler)
+        {
+            handlers.RemoveAll(item => !item.IsAlive || item.Equals(handler));
+        }
+
+        public void Invoke(object sender, EventArgs args)
+        {
+            var copied = handlers.ToArray();
+            foreach (var item in copied)
+            {
+                if (item.IsAlive)
+                {
+                    item.Invoke(sender, args);
+                    continue;
+                }
+                handlers.Remove(item);
+            }
+        }
+
+        internal class WeakHandler<U>
+        {
+            private WeakReference weakReference;
+            private MethodInfo methodInfo;
+
+            public WeakHandler(U handler)
+            {
+                Delegate d = (Delegate)(object)handler;
+                if (d.Target != null) weakReference = new WeakReference(d.Target);
+                methodInfo = d.Method;
+            }
+
+            public bool Equals(U handler)
+            {
+                Delegate other = (Delegate)(object)handler;
+                return other != null && other.Target == weakReference?.Target && other.Method.Equals(methodInfo);
+            }
+
+            public bool IsAlive => weakReference == null || weakReference.IsAlive;
+
+            public void Invoke(params object[] args)
+            {
+                if (weakReference == null)
+                {
+                    Delegate.CreateDelegate(typeof(U), methodInfo).DynamicInvoke(args);
+                }
+                else
+                {
+                    // Because GC is done in other thread,
+                    // it needs to check again that the reference is still alive before calling method.
+                    // To do that, the reference should be assigned to the local variable first.
+                    var localRefCopied = weakReference.Target;
+
+                    // Please do not change this to if (weakReference.Target != null)
+                    if (localRefCopied != null) Delegate.CreateDelegate(typeof(U), localRefCopied, methodInfo).DynamicInvoke(args);
+                }
+            }
+        }
+    }
+}
index f6f8ade..25c023b 100755 (executable)
@@ -1636,11 +1636,11 @@ namespace Tizen.NUI.BaseComponents
 
             if (view.themeChangeSensitive)
             {
-                ThemeManager.ThemeChangedInternal += view.OnThemeChanged;
+                ThemeManager.ThemeChangedInternal.Add(view.OnThemeChanged);
             }
             else
             {
-                ThemeManager.ThemeChangedInternal -= view.OnThemeChanged;
+                ThemeManager.ThemeChangedInternal.Remove(view.OnThemeChanged);
             }
         },
         defaultValueCreator: (bindable) =>
index 4c24089..75e14a3 100755 (executable)
@@ -1129,7 +1129,7 @@ namespace Tizen.NUI.BaseComponents
                 selectorData?.Reset(this);
                 if (themeChangeSensitive)
                 {
-                    ThemeManager.ThemeChanged -= OnThemeChanged;
+                    ThemeManager.ThemeChangedInternal.Remove(OnThemeChanged);
                 }
             }
 
index 90ec77e..061056b 100755 (executable)
@@ -20,6 +20,7 @@ using System;
 using System.Collections.Generic;
 using System.ComponentModel;
 using System.IO;
+using Tizen.NUI;
 using Tizen.NUI.BaseComponents;
 
 namespace Tizen.NUI
@@ -82,7 +83,7 @@ namespace Tizen.NUI
         /// <summary>
         /// Internal one should be called before calling public ThemeChanged
         /// </summary>
-        internal static event EventHandler<ThemeChangedEventArgs> ThemeChangedInternal;
+        internal static WeakEvent<EventHandler<ThemeChangedEventArgs>> ThemeChangedInternal = new WeakEvent<EventHandler<ThemeChangedEventArgs>>();
 
         internal static Theme CurrentTheme
         {
@@ -344,7 +345,7 @@ namespace Tizen.NUI
 
         private static void NotifyThemeChanged()
         {
-            ThemeChangedInternal?.Invoke(null, new ThemeChangedEventArgs(CurrentTheme?.Id));
+            ThemeChangedInternal.Invoke(null, new ThemeChangedEventArgs(CurrentTheme?.Id));
             ThemeChanged?.Invoke(null, new ThemeChangedEventArgs(CurrentTheme?.Id));
         }
     }