reactiveui / ReactiveUI

An advanced, composable, functional reactive model-view-viewmodel framework for all .NET platforms that is inspired by functional reactive programming. ReactiveUI allows you to abstract mutable state away from your user interfaces, express the idea around a feature in one readable place and improve the testability of your application.
https://www.reactiveui.net
MIT License
8.07k stars 1.12k forks source link

Xamarin.iOS Linker break RxApp. #395

Closed nverinaud closed 10 years ago

nverinaud commented 10 years ago

Using Xamarin.iOS.

When building in Release | Ad Hoc | App Store for the device the Linker comes in and seems to remove necessary methods in RxApp. Setting "Don't link" option when building resolve the issue but it would be great if the Linker could do his job correctly. A hint is to mark "hard-to-discover" methods using the [Preserve] attribute.

Unfortunately I can't easily figure out which methods are "hard-to-discover".

Here is a stack trace when building starter-mobile in Release for the device :

System.TypeInitializationException: An exception was thrown by the type initializer for ReactiveUI.RxApp ---> System.Exception: Object reference not set to an instance of an object
  at ReactiveUI.DependencyResolverMixins.GetService[ILogger] (IDependencyResolver This, System.String contract) [0x00000] in <filename unknown>:0
  at ReactiveUI.DefaultLogManager+<DefaultLogManager>c__AnonStorey5F.<>m__133 (System.Type type, System.Object _) [0x00000] in /Users/paul/github/native-dep-rebuild/deps/reactiveui/ReactiveUI/Logging-iOS.cs:83
  at ReactiveUI.MemoizingMRUCache`2[System.Type,ReactiveUI.IFullLogger].Get (System.Type key, System.Object context) [0x00000] in <filename unknown>:0
  at ReactiveUI.MemoizingMRUCache`2[System.Type,ReactiveUI.IFullLogger].Get (System.Type key) [0x00000] in <filename unknown>:0
  at ReactiveUI.DefaultLogManager.GetLogger (System.Type type) [0x0003c] in /Users/paul/github/native-dep-rebuild/deps/reactiveui/ReactiveUI/Logging-iOS.cs:99
  at ReactiveUI.LogHost.get_Default () [0x00034] in /Users/paul/github/native-dep-rebuild/deps/reactiveui/ReactiveUI/Logging-iOS.cs:173
  at ReactiveUI.RxApp..cctor () [0x00076] in /Users/paul/github/native-dep-rebuild/deps/reactiveui/ReactiveUI/RxApp.cs:75
  --- End of inner exception stack trace ---
  at CoEPOC.iOS.AppDelegate.SetupDependencyInjection (MonoTouch.Foundation.NSDictionary options) [0x00006] in /Users/nicolas/Developement/Apps/CoE POC/Mobile/CoEPOC-iOS/AppDelegate.cs:67
  at CoEPOC.iOS.AppDelegate.FinishedLaunching (MonoTouch.UIKit.UIApplication app, MonoTouch.Foundation.NSDictionary options) [0x00000] in /Users/nicolas/Developement/Apps/CoE POC/Mobile/CoEPOC-iOS/AppDelegate.cs:33
  at at (wrapper managed-to-native) MonoTouch.UIKit.UIApplication:UIApplicationMain (int,string[],intptr,intptr)
  at MonoTouch.UIKit.UIApplication.Main (System.String[] args, System.String principalClassName, System.String delegateClassName) [0x0004c] in /Developer/MonoTouch/Source/monotouch/src/UIKit/UIApplication.cs:38
  at CoEPOC.Application.Main (System.String[] args) [0x00000] in /Users/nicolas/Developement/Apps/CoE POC/Mobile/CoEPOC-iOS/Main.cs:23
anaisbetts commented 10 years ago

Hm, I'll look into this. You might have to use Link SDK Only with RxUI, as it will always use a lot of reflection and other things a linker won't pick up

nverinaud commented 10 years ago

Okay, Thanks !

If I can help spot reflection issues, tell me just how to spot them and I'll do my best :)

anaisbetts commented 10 years ago

I probably need to talk to the Mono guys, it'd be nice if libraries could embed hint information (maybe we just need [Preserve] tags? Are they respected in libraries?)

nverinaud commented 10 years ago

Should try it, it is suggested by the stacktrace... :')

nverinaud commented 10 years ago

Even if I link SDK assemblies only it crashes.

I just spot this one : System.MissingMethodException: Default constructor not found for type System.ComponentModel.ReferenceConverter.

anaisbetts commented 10 years ago

Are you using Alpha build of Xamarin? If not you probably need to rebuild RxUI

nverinaud commented 10 years ago

No and not working after rebuilt.

nverinaud commented 10 years ago

It is a huge issue. I have to drop this library for now because having a 40Mb app instead of 8Mb is not possible. :(

anaisbetts commented 10 years ago

@nverinaud Alright, I was able to repro your bug, try dropping this into your AppDelegate's FinishedLaunching:

// NB: GrossHackAlertTiem™:
//
// Monotouch appears to not load assemblies when you request them 
// via Type.GetType, unlike every other platform (even 
// Xamarin.Android). So, we've got to manually do what RxUI and 
// Akavache would normally do for us
var r = new ModernDependencyResolver();
(new ReactiveUI.Registrations()).Register((f,t) => r.Register(f, t));
(new ReactiveUI.Cocoa.Registrations()).Register((f,t) => r.Register(f, t));
(new ReactiveUI.Mobile.Registrations()).Register((f,t) => r.Register(f, t));
(new Akavache.Registrations()).Register(r.Register);
(new Akavache.Mobile.Registrations()).Register(r.Register);
(new Akavache.Sqlite3.Registrations()).Register(r.Register);
RxApp.DependencyResolver = r;
normanhh3 commented 10 years ago

I'm not sure how the app size relates to the code that Paul posted. Is that another issue?

When you are comparing sizes, are you comparing a native objective-c app versus a Mono-based app?

I'm thinking about porting a currently native app on Android and iOS to ReactiveUI and Xamarin, but size is a bit of a concern to me as well.

On Thu, Dec 5, 2013 at 8:13 AM, Paul Betts notifications@github.com wrote:

@nverinaud https://github.com/nverinaud Alright, I was able to repro your bug, try dropping this into your AppDelegate's FinishedLaunching:

// NB: GrossHackAlertTiem™://// Monotouch appears to not load assemblies when you request them // via Type.GetType, unlike every other platform (even // Xamarin.Android). So, we've got to manually do what RxUI and // Akavache would normally do for usvar r = new ModernDependencyResolver();(new ReactiveUI.Registrations()).Register((f,t) => r.Register(f, t));(new ReactiveUI.Cocoa.Registrations()).Register((f,t) => r.Register(f, t));(new ReactiveUI.Mobile.Registrations()).Register((f,t) => r.Register(f, t));(new Akavache.Registrations()).Register(r.Register);(new Akavache.Mobile.Registrations()).Register(r.Register);(new Akavache.Sqlite3.Registrations()).Register(r.Register);RxApp.DependencyResolver = r;

— Reply to this email directly or view it on GitHubhttps://github.com/reactiveui/ReactiveUI/issues/395#issuecomment-29896193 .

nverinaud commented 10 years ago

Mono adds approx 2-3Mb to apps with the linker enabled. If you disable the linker (to prevent crash with ReactiveUI) the app weights approx 30Mb more >.<

@paulcbetts Just for testing I tried your starter-mobile repo (https://github.com/paulcbetts/starter-mobile). I removed Akavache.SQlite3 because it does not build and I use the linker in 'Release' mode. I can provide a sample repo preconfigured if you want.

nverinaud commented 10 years ago

Here is the test project : https://github.com/nverinaud/reactiveui-ios-linker-crash

normanhh3 commented 10 years ago

Thanks for the clarification. :-)

On Thu, Dec 5, 2013 at 8:52 AM, Nicolas VERINAUD notifications@github.comwrote:

Here is the test project : https://github.com/nverinaud/reactiveui-ios-linker-crash

— Reply to this email directly or view it on GitHubhttps://github.com/reactiveui/ReactiveUI/issues/395#issuecomment-29898685 .

anaisbetts commented 10 years ago

Works for me once I fix the stripped setter by adding

TheGuid.Text = "";

ReactiveUI is always going to have a few issues with the linker set to "All Assemblies" because it uses reflection to set properties. They're usually not hard to get around though

nverinaud commented 10 years ago

Okay for this project it was easy. I don't link against "All Assemblies", only against "SDK Assemblies only".

I do not agree though that it is not "hard" to get around. This one for example :disappointed: :

System.ArgumentException: Set Method not found for 'Placeholder'
  at System.Reflection.MonoProperty.SetValue (System.Object obj, System.Object value, BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] index, System.Globalization.CultureInfo culture) [0x00000] in <filename unknown>:0 
  at System.Reflection.PropertyInfo.SetValue (System.Object obj, System.Object value, System.Object[] index) [0x00000] in <filename unknown>:0 
  at ReactiveUI.Reflection+<propWriterCache>c__AnonStorey41.<>m__B9 (System.Object y, System.Object v) [0x00000] in <filename unknown>:0 
  at ReactiveUI.PropertyBinderImplementation+<bindToDirect>c__AnonStorey55`2[MonoTouch.UIKit.UISearchBar,System.String].<>m__103 (System.String x) [0x00000] in <filename unknown>:0 
  at System.Reactive.AnonymousSafeObserver`1[System.String].OnNext (System.String value) [0x00000] in <filename unknown>:0 
  at System.Reactive.Linq.Observαble.SelectMany`2+_+ι[System.String,System.String].OnNext (System.String value) [0x00000] in <filename unknown>:0 
  at System.Reactive.Linq.Observαble.Return`1+_[System.String].Invoke () [0x00000] in <filename unknown>:0 
  at System.Reactive.Concurrency.Scheduler.Invoke (IScheduler scheduler, System.Action action) [0x00000] in <filename unknown>:0 
  at System.Reactive.Concurrency.ImmediateScheduler.Schedule[Action] (System.Action state, System.Func`3 action) [0x00000] in <filename unknown>:0 
  at System.Reactive.Concurrency.Scheduler.Schedule (IScheduler scheduler, System.Action action) [0x00000] in <filename unknown>:0 
  at System.Reactive.Linq.Observαble.Return`1+_[System.String].Run () [0x00000] in <filename unknown>:0 
  at System.Reactive.Linq.Observαble.Return`1[System.String].Run (IObserver`1 observer, IDisposable cancel, System.Action`1 setSink) [0x00000] in <filename unknown>:0 
  at System.Reactive.Producer`1[System.String].SubscribeRaw (IObserver`1 observer, Boolean enableSafeguard) [0x00000] in <filename unknown>:0 
  at System.ObservableExtensions.SubscribeSafe[String] (IObservable`1 source, IObserver`1 observer) [0x00000] in <filename unknown>:0 
  at System.Reactive.Linq.Observαble.SelectMany`2+_[System.String,System.String].SubscribeInner (IObservable`1 inner) [0x00000] in <filename unknown>:0 
  at System.Reactive.Linq.Observαble.SelectMany`2+_[System.String,System.String].OnNext (System.String value) [0x00000] in <filename unknown>:0 
  at System.Reactive.Linq.Observαble.Select`2+_[ReactiveUI.IObservedChange`2[CoEPOC.Core.SearchViewModel,System.String],System.String].OnNext (IObservedChange`2 value) [0x00000] in <filename unknown>:0 
  at System.Reactive.Linq.Observαble.DistinctUntilChanged`2+_[ReactiveUI.IObservedChange`2[CoEPOC.Core.SearchViewModel,System.String],System.String].OnNext (IObservedChange`2 value) [0x00000] in <filename unknown>:0 
  at System.Reactive.Linq.Observαble.Select`2+_[ReactiveUI.IObservedChange`2[System.Object,System.Object],ReactiveUI.IObservedChange`2[CoEPOC.Core.SearchViewModel,System.String]].OnNext (IObservedChange`2 value) [0x00000] in <filename unknown>:0 
  at System.Reactive.Linq.Observαble.Where`1+_[ReactiveUI.IObservedChange`2[System.Object,System.Object]].OnNext (IObservedChange`2 value) [0x00000] in <filename unknown>:0 
  at System.Reactive.Linq.Observαble.Switch`1+_+ι[ReactiveUI.IObservedChange`2[System.Object,System.Object]].OnNext (IObservedChange`2 value) [0x00000] in <filename unknown>:0 
  at System.Reactive.Linq.Observαble.Concat`1+_[ReactiveUI.IObservedChange`2[System.Object,System.Object]].OnNext (IObservedChange`2 value) [0x00000] in <filename unknown>:0 
  at System.Reactive.Linq.Observαble.ToObservable`1+_[ReactiveUI.IObservedChange`2[System.Object,System.Object]].LoopRec (System.Reactive.Linq.Observαble.State state, System.Action`1 recurse) [0x00000] in <filename unknown>:0 
  at System.Reactive.Concurrency.Scheduler+<InvokeRec1>c__AnonStoreyA`1[System.Reactive.Linq.Observαble.ToObservable`1+_+State[ReactiveUI.IObservedChange`2[System.Object,System.Object]]].<>m__C (System.Reactive.Linq.Observαble.State state1) [0x00000] in <filename unknown>:0 
  at System.Reactive.Concurrency.Scheduler.InvokeRec1[State] (IScheduler scheduler, Pair`2 pair) [0x00000] in <filename unknown>:0 
  at (wrapper delegate-invoke) System.Func`3<System.Reactive.Concurrency.IScheduler, System.Reactive.Concurrency.Scheduler/Pair`2<System.Reactive.Linq.Observαble.ToObservable`1/_/State<ReactiveUI.IObservedChange`2<object, object>>, System.Action`2<System.Reactive.Linq.Observαble.ToObservable`1/_/State<ReactiveUI.IObservedChange`2<object, object>>, System.Action`1<System.Reactive.Linq.Observαble.ToObservable`1/_/State<ReactiveUI.IObservedChange`2<object, object>>>>>, System.IDisposable>:invoke_TResult__this___T1_T2 (System.Reactive.Concurrency.IScheduler,System.Reactive.Concurrency.Scheduler/Pair`2<System.Reactive.Linq.Observαble.ToObservable`1/_/State<ReactiveUI.IObservedChange`2<object, object>>, System.Action`2<System.Reactive.Linq.Observαble.ToObservable`1/_/State<ReactiveUI.IObservedChange`2<object, object>>, System.Action`1<System.Reactive.Linq.Observαble.ToObservable`1/_/State<ReactiveUI.IObservedChange`2<object, object>>>>>)
  at System.Reactive.Concurrency.ImmediateScheduler.Schedule[Pair`2] (Pair`2 state, System.Func`3 action) [0x00000] in <filename unknown>:0 
  at System.Reactive.Concurrency.Scheduler.Schedule[State] (IScheduler scheduler, System.Reactive.Linq.Observαble.State state, System.Action`2 action) [0x00000] in <filename unknown>:0 
  at System.Reactive.Linq.Observαble.ToObservable`1+_[ReactiveUI.IObservedChange`2[System.Object,System.Object]].Run () [0x00000] in <filename unknown>:0 
  at System.Reactive.Linq.Observαble.ToObservable`1[ReactiveUI.IObservedChange`2[System.Object,System.Object]].Run (IObserver`1 observer, IDisposable cancel, System.Action`1 setSink) [0x00000] in <filename unknown>:0 
  at System.Reactive.Producer`1[ReactiveUI.IObservedChange`2[System.Object,System.Object]].SubscribeRaw (IObserver`1 observer, Boolean enableSafeguard) [0x00000] in <filename unknown>:0 
  at System.ObservableExtensions.SubscribeSafe[IObservedChange`2] (IObservable`1 source, IObserver`1 observer) [0x00000] in <filename unknown>:0 
  at System.Reactive.TailRecursiveSink`1[ReactiveUI.IObservedChange`2[System.Object,System.Object]].MoveNext () [0x00000] in <filename unknown>:0 
  at System.Reactive.Concurrency.AsyncLock.Wait (System.Action action) [0x00000] in <filename unknown>:0 

Thanks for your help.

It's unfortunate that reflection is not well handled by the linker :worried:

nverinaud commented 10 years ago

Hum… It's actually not that hard, you're right. Needs a bit of documentation and explanations (maybe in a "warning" section somewhere for Xamarin.iOS).

anaisbetts commented 10 years ago

@nverinaud Yeah, you don't even need to set a live object, just create a dummy method that sets a parameter and never call that method

patlac commented 9 years ago

@nverinaud @paulcbetts I exactly run into "System.ArgumentException: Set Method not found for 'Placeholder'" and wondered why it worked on the iOS Sim, but not on the device (throwing that exception). Can you give a concrete example on how to "...just create a dummy method that sets a parameter ..." ? I'm a bit lost here... (And of course would like to use linking :-)

nverinaud commented 9 years ago

Just create a class with a method and do not use it. Here is an example of what I do.

private static class LinkerFixer
{
    private static void KeepTheseStuff()
    {
        UITextField textField = null;
        textField.Placeholder = "";
    }
}

This prevents the linker from removing the Placholder setter from UITextField.

To identify which methods may be removed simply look where in your code you use BindTo.

patlac commented 9 years ago

@nverinaud Not much voodo indeed - perfect! Thx ;-)