From ac13de8bfe69cd467e1037746b25f829b9325721 Mon Sep 17 00:00:00 2001 From: Jiyun Yang Date: Fri, 20 Nov 2020 17:39:04 +0900 Subject: [PATCH] [NUI] Use weak reference for theme changed event 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 --- src/Tizen.NUI/src/internal/WeakEvent.cs | 91 ++++++++++++++++++++++ .../public/BaseComponents/ViewBindableProperty.cs | 4 +- .../src/public/BaseComponents/ViewInternal.cs | 2 +- src/Tizen.NUI/src/public/Theme/ThemeManager.cs | 5 +- 4 files changed, 97 insertions(+), 5 deletions(-) create mode 100644 src/Tizen.NUI/src/internal/WeakEvent.cs diff --git a/src/Tizen.NUI/src/internal/WeakEvent.cs b/src/Tizen.NUI/src/internal/WeakEvent.cs new file mode 100644 index 0000000..de710a3 --- /dev/null +++ b/src/Tizen.NUI/src/internal/WeakEvent.cs @@ -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 + { + private List> handlers = new List>(); + + public void Add(T handler) + { + handlers.Add(new WeakHandler(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 + { + 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); + } + } + } + } +} diff --git a/src/Tizen.NUI/src/public/BaseComponents/ViewBindableProperty.cs b/src/Tizen.NUI/src/public/BaseComponents/ViewBindableProperty.cs index f6f8ade..25c023b 100755 --- a/src/Tizen.NUI/src/public/BaseComponents/ViewBindableProperty.cs +++ b/src/Tizen.NUI/src/public/BaseComponents/ViewBindableProperty.cs @@ -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) => diff --git a/src/Tizen.NUI/src/public/BaseComponents/ViewInternal.cs b/src/Tizen.NUI/src/public/BaseComponents/ViewInternal.cs index 4c24089..75e14a3 100755 --- a/src/Tizen.NUI/src/public/BaseComponents/ViewInternal.cs +++ b/src/Tizen.NUI/src/public/BaseComponents/ViewInternal.cs @@ -1129,7 +1129,7 @@ namespace Tizen.NUI.BaseComponents selectorData?.Reset(this); if (themeChangeSensitive) { - ThemeManager.ThemeChanged -= OnThemeChanged; + ThemeManager.ThemeChangedInternal.Remove(OnThemeChanged); } } diff --git a/src/Tizen.NUI/src/public/Theme/ThemeManager.cs b/src/Tizen.NUI/src/public/Theme/ThemeManager.cs index 90ec77e..061056b 100755 --- a/src/Tizen.NUI/src/public/Theme/ThemeManager.cs +++ b/src/Tizen.NUI/src/public/Theme/ThemeManager.cs @@ -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 /// /// Internal one should be called before calling public ThemeChanged /// - internal static event EventHandler ThemeChangedInternal; + internal static WeakEvent> ThemeChangedInternal = new WeakEvent>(); 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)); } } -- 2.7.4