Search code examples
c#xamarin.iosmvvmcrossuislider

MvvmCross - Getting TargetInvocationException and SIGABRT while disposing a binding in UISlider


I'm working on a project with Xamarin.iOS and MvvmCross (v5.6.3) and I have a very typical UISlider on one view controller with its value binded to a float property on its view-model.

set.Bind(MySlider).For(x => x.Value).To(vm => vm.FloatProperty).TwoWay();

When the view unloads and try to dispose the existing bindings (like while navigating to another view-model) I'm getting the following unhandled exception with a SIGABRT:

Unhandled Exception:
System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> UIKit.UIKitThreadAccessException: UIKit Consistency error: you are calling a UIKit method that can only be invoked from the UI thread.
  at UIKit.UIApplication.EnsureUIThread () [0x00023] in /Library/Frameworks/Xamarin.iOS.framework/Versions/11.9.1.24/src/Xamarin.iOS/UIKit/UIApplication.cs:88 
  at UIKit.UIControl.RemoveTarget (Foundation.NSObject target, System.IntPtr sel, UIKit.UIControlEvent events) [0x00000] in /Library/Frameworks/Xamarin.iOS.framework/Versions/11.9.1.24/src/Xamarin.iOS/UIKit/UIControl.g.cs:235 
  at UIKit.UIControl.RemoveTarget (System.EventHandler notification, UIKit.UIControlEvent events) [0x00057] in /Library/Frameworks/Xamarin.iOS.framework/Versions/11.9.1.24/src/Xamarin.iOS/UIKit/UIControl.cs:116 
  at UIKit.UIControl.remove_ValueChanged (System.EventHandler value) [0x00000] in /Library/Frameworks/Xamarin.iOS.framework/Versions/11.9.1.24/src/Xamarin.iOS/UIKit/UIControl.cs:209 
  at (wrapper managed-to-native) System.Reflection.MonoMethod.InternalInvoke(System.Reflection.MonoMethod,object,object[],System.Exception&)
  at System.Reflection.MonoMethod.Invoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) [0x00032] in /Library/Frameworks/Xamarin.iOS.framework/Versions/11.9.1.24/src/Xamarin.iOS/mcs/class/corlib/System.Reflection/MonoMethod.cs:305 
   --- End of inner exception stack trace ---
  at System.Reflection.MonoMethod.Invoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) [0x00046] in /Library/Frameworks/Xamarin.iOS.framework/Versions/11.9.1.24/src/Xamarin.iOS/mcs/class/corlib/System.Reflection/MonoMethod.cs:313 
  at System.Reflection.MethodBase.Invoke (System.Object obj, System.Object[] parameters) [0x00000] in /Library/Frameworks/Xamarin.iOS.framework/Versions/11.9.1.24/src/Xamarin.iOS/mcs/class/referencesource/mscorlib/system/reflection/methodbase.cs:229 
  at MvvmCross.Platform.WeakSubscription.MvxWeakEventSubscription`1[TSource].RemoveEventHandler () [0x00024] in <6adc0d5857264558a9d45778a78ae02a>:0 
  at MvvmCross.Platform.WeakSubscription.MvxWeakEventSubscription`1[TSource].Dispose (System.Boolean disposing) [0x00003] in <6adc0d5857264558a9d45778a78ae02a>:0 
  at MvvmCross.Platform.WeakSubscription.MvxWeakEventSubscription`1[TSource].Dispose () [0x00000] in <6adc0d5857264558a9d45778a78ae02a>:0 
  at MvvmCross.Binding.iOS.Target.MvxUISliderValueTargetBinding.Dispose (System.Boolean isDisposing) [0x0001b] in <614c9ef828c14ba687a40ec2656f480f>:0 
  at MvvmCross.Binding.Bindings.MvxBinding.Finalize () [0x00000] in <866b1e46764b48aab0d408952a6f006f>:0 
2018-04-09 23:15:20.210 MyProject.iOS[73770:3302608] Unhandled managed exception:
Exception has been thrown by the target of an invocation. (System.Reflection.TargetInvocationException)
  at System.Reflection.MonoMethod.Invoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) [0x00046] in /Library/Frameworks/Xamarin.iOS.framework/Versions/11.9.1.24/src/Xamarin.iOS/mcs/class/corlib/System.Reflection/MonoMethod.cs:313 
  at System.Reflection.MethodBase.Invoke (System.Object obj, System.Object[] parameters) [0x00000] in /Library/Frameworks/Xamarin.iOS.framework/Versions/11.9.1.24/src/Xamarin.iOS/mcs/class/referencesource/mscorlib/system/reflection/methodbase.cs:229 
  at MvvmCross.Platform.WeakSubscription.MvxWeakEventSubscription`1[TSource].RemoveEventHandler () [0x00024] in <6adc0d5857264558a9d45778a78ae02a>:0 
  at MvvmCross.Platform.WeakSubscription.MvxWeakEventSubscription`1[TSource].Dispose (System.Boolean disposing) [0x00003] in <6adc0d5857264558a9d45778a78ae02a>:0 
  at MvvmCross.Platform.WeakSubscription.MvxWeakEventSubscription`1[TSource].Dispose () [0x00000] in <6adc0d5857264558a9d45778a78ae02a>:0 
  at MvvmCross.Binding.iOS.Target.MvxUISliderValueTargetBinding.Dispose (System.Boolean isDisposing) [0x0001b] in <614c9ef828c14ba687a40ec2656f480f>:0 
  at MvvmCross.Binding.Bindings.MvxBinding.Finalize () [0x00000] in <866b1e46764b48aab0d408952a6f006f>:0 
 --- inner exception ---
UIKit Consistency error: you are calling a UIKit method that can only be invoked from the UI thread. (UIKit.UIKitThreadAccessException)
  at UIKit.UIApplication.EnsureUIThread () [0x00023] in /Library/Frameworks/Xamarin.iOS.framework/Versions/11.9.1.24/src/Xamarin.iOS/UIKit/UIApplication.cs:88 
  at UIKit.UIControl.RemoveTarget (Foundation.NSObject target, System.IntPtr sel, UIKit.UIControlEvent events) [0x00000] in /Library/Frameworks/Xamarin.iOS.framework/Versions/11.9.1.24/src/Xamarin.iOS/UIKit/UIControl.g.cs:235 
  at UIKit.UIControl.RemoveTarget (System.EventHandler notification, UIKit.UIControlEvent events) [0x00057] in /Library/Frameworks/Xamarin.iOS.framework/Versions/11.9.1.24/src/Xamarin.iOS/UIKit/UIControl.cs:116 
  at UIKit.UIControl.remove_ValueChanged (System.EventHandler value) [0x00000] in /Library/Frameworks/Xamarin.iOS.framework/Versions/11.9.1.24/src/Xamarin.iOS/UIKit/UIControl.cs:209 
  at (wrapper managed-to-native) System.Reflection.MonoMethod.InternalInvoke(System.Reflection.MonoMethod,object,object[],System.Exception&)
  at System.Reflection.MonoMethod.Invoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) [0x00032] in /Library/Frameworks/Xamarin.iOS.framework/Versions/11.9.1.24/src/Xamarin.iOS/mcs/class/corlib/System.Reflection/MonoMethod.cs:305 
2018-04-09 23:15:20.210 MyProject.iOS[73770:3302608] critical: Stacktrace:

2018-04-09 23:15:20.211 MyProject.iOS[73770:3302608] critical: 
Native stacktrace:

2018-04-09 23:15:20.212 MyProject.iOS[73770:3302608] critical:  0   MyProject.iOS                       0x00000001038b4574 mono_handle_native_crash + 244
2018-04-09 23:15:20.212 MyProject.iOS[73770:3302608] critical:  1   libsystem_platform.dylib            0x000000010bbecf5a _sigtramp + 26
2018-04-09 23:15:20.213 MyProject.iOS[73770:3302608] critical:  2   ???                                 0x0000000103dd308a 0x0 + 4359794826
2018-04-09 23:15:20.213 MyProject.iOS[73770:3302608] critical:  3   libsystem_c.dylib                   0x000000010b8820eb abort + 127
2018-04-09 23:15:20.213 MyProject.iOS[73770:3302608] critical:  4   MyProject.iOS                       0x0000000103a5b2df xamarin_unhandled_exception_handler + 47
2018-04-09 23:15:20.214 MyProject.iOS[73770:3302608] critical:  5   MyProject.iOS                       0x000000010391b854 mono_invoke_unhandled_exception_hook + 148
2018-04-09 23:15:20.214 MyProject.iOS[73770:3302608] critical:  6   MyProject.iOS                       0x00000001039c2a3e mono_thread_internal_unhandled_exception + 110
2018-04-09 23:15:20.215 MyProject.iOS[73770:3302608] critical:  7   MyProject.iOS                       0x000000010391d15a mono_gc_run_finalize + 842
2018-04-09 23:15:20.215 MyProject.iOS[73770:3302608] critical:  8   MyProject.iOS                       0x00000001039fc9ba sgen_gc_invoke_finalizers + 234
2018-04-09 23:15:20.215 MyProject.iOS[73770:3302608] critical:  9   MyProject.iOS                       0x000000010391eae4 finalizer_thread + 756
2018-04-09 23:15:20.215 MyProject.iOS[73770:3302608] critical:  10  MyProject.iOS                       0x00000001039c3600 start_wrapper + 704
2018-04-09 23:15:20.215 MyProject.iOS[73770:3302608] critical:  11  libsystem_pthread.dylib             0x000000010bbfe6c1 _pthread_body + 340
2018-04-09 23:15:20.216 MyProject.iOS[73770:3302608] critical:  12  libsystem_pthread.dylib             0x000000010bbfe56d _pthread_body + 0
2018-04-09 23:15:20.216 MyProject.iOS[73770:3302608] critical:  13  libsystem_pthread.dylib             0x000000010bbfdc5d thread_start + 13
2018-04-09 23:15:20.216 MyProject.iOS[73770:3302608] critical: 
=================================================================
Got a SIGABRT while executing native code. This usually indicates
a fatal error in the mono runtime or one of the native libraries 
used by your application.
=================================================================

The binding works fine, this happens only when I try to navigate to another view-model the view is being unloaded.

Am I missing something? Or is this maybe a bug on MvvmCross?

Edit: Finally checked that this issue occurs when the view is being unloaded, regardless I'm navigating to another view-model or not. I've removed the references where I'm pointing to a navigation issue.


Solution

  • There is currently a bug in MvvmCross 5.x.x with respect to the UISlider. The issue has been fixed with PR 2750 which will ship in version 6.0.0

    In the meantime, you should be able to override the binding to fix it for your project by creating a custom binding with the fix. The issue is in the Dispose.

    public class FixedMvxUISliderValueTargetBinding : MvxPropertyInfoTargetBinding<UISlider>
    {
        private IDisposable _subscription;
    
        public FixedMvxUISliderValueTargetBinding(object target, PropertyInfo targetPropertyInfo) 
            : base(target, targetPropertyInfo) { }
    
        protected override void SetValueImpl(object target, object value)
        {
            var view = target as UISlider;
            if (view == null) return;
            view.Value = (float)value;
        }
    
        private void HandleSliderValueChanged(object sender, EventArgs e)
        {
            var view = View;
            if (view == null) return;
            FireValueChanged(view.Value);
        }
    
        public override MvxBindingMode DefaultMode => MvxBindingMode.TwoWay;
    
        public override void SubscribeToEvents()
        {
            var slider = View;
            if (slider == null)
            {
                MvxBindingTrace.Trace(
                    MvxTraceLevel.Error, 
                    "Error - UISlider is null in MvxUISliderValueTargetBinding");
                return;
            }
            _subscription = slider.WeakSubscribe(
                nameof(slider.ValueChanged), 
                HandleSliderValueChanged);
        }
    
        protected override void Dispose(bool isDisposing)
        {
            base.Dispose(isDisposing);
            if (isDisposing)
            {
                _subscription?.Dispose();
                _subscription = null;
            }
        }
    }
    

    Then in your Setup.cs register the binding. Note, make sure that you call base.FillTargetFactories first.

    protected override void FillTargetFactories(IMvxTargetBindingFactoryRegistry registry)
    {
        base.FillTargetFactories(registry);
        registry.RegisterPropertyInfoBindingFactory(
            typeof(FixedMvxUISliderValueTargetBinding),
            typeof(UISlider),
            nameof(UISlider.Value));
    }