Reputation: 2387
I'm getting this exception in my MonoTouch app and I can't seem to fix it. I've been trying for about 6 hours now and I'm having no luck.
My understanding of this exception is that an object is being referenced (or trying to be referenced) by MonoTouch but Garbage Collection has already disposed of it. Therefore it is looking to build a reference to it again using a constructor which I haven't set in the class, using a pointer.
Originally I thought that adding that constructor would be enough until I did some research and realised that at best, it would be a temporary bandage. What I'm finding strange is that as far as I can see I am holding a reference to everything. I've even gone a bit overboard and started creating member variables for things that don't necessarily need to be kept to try and capture the exception, but still nothing.
The error occurs when I load a ViewController, trigger a delegate which pushes a new ViewController onto the NavigationController (thus leaving the screen), hitting the back button and moving onto another view again. This consistently happens the same way and will always crash at this point, though not necessarily at the exact same time due to the way GC works I suppose.
The app is quite simple so far. The first screen loads 5 different charts using ShinobiCharts. Data Sources and Delegates are set in the ViewController and a ViewModel is sent to the View which then adds the charts to the subview. The exception occurs in ColumnChart (a custom made class inheriting from ShinobiChart). The exception will always be thrown on these controls never the View or the ViewController. However, which chart causes the exception is seemingly random every time.
How I'm storing the charts looks like this:
public class HomeViewModel
{
public ColumnChart MeetingsChart { get; set; }
public ColumnChart FirmDataChart { get; set; }
public ColumnChart SystemUseChart { get; set; }
public BarChart IllustrationsChart { get; set; }
public PieChart TermsOfBusinessChart { get; set; }
public ColumnChartDataSource MeetingsChartDataSource { get; set; }
public ColumnChartDataSource SystemUseChartDataSource { get; set; }
public StackChartDataSource FirmDataChartDataSource { get; set; }
public BarChartDataSource IllustrationsChartDataSource { get; set; }
public PieChartDataSource TermsOfBusinessDataSource { get; set; }
public BarChartDataProvider TermsOfBusinessDataProvider { get; set; }
public DashboardMeetingChartDelegate MeetingsChartDelegate { get; set; }
public ColumnChartDelegate SystemUseChartDelegate { get; set; }
public PieChartDelegate TermsOfBusinessDelegate { get; set; }
}
This class is then implemented in the Controller like this: (NOTE: Lots of code was stripped out as I didn't think it was needed)
public class HomeController : UIViewController
{
private HomeView _homeView;
private HomeViewModel _homeViewModel;
…..
public HomeController()
{
_homeViewModel = new HomeViewModel();
}
….
private void LoadCharts()
{
_homeViewModel.MeetingsChart = LoadMeetingsChart();
_homeViewModel.FirmDataChart = LoadFirmDataChart();
_homeViewModel.SystemUseChart = LoadSystemUseChart();
_homeViewModel.IllustrationsChart = LoadIllustrationsChart();
_homeViewModel.TermsOfBusinessChart = LoadTermsOfBusinessChart();
}
private ColumnChart LoadMeetingsChart()
{
ColumnChart meetingsChart = new ColumnChart(RectangleF.Empty);
meetingsChart.DataSource = _homeViewModel.MeetingsChartDataSource;
meetingsChart.Delegate = _homeViewModel.MeetingsChartDelegate;
meetingsChart.LicenseKey = LICENSE_KEY;
return meetingsChart;
}
}
Finally, the View:
public class HomeView : UIView
{
private HomeViewModel _homeViewModel;
public HomeView(RectangleF frame, UINavigationController navigationController, HomeViewModel homeViewModel)
: base(frame)
{
this._homeViewModel = homeViewModel;
this.BackgroundColor = UIColor.FromRGB(20, 20, 20);
this.Title = "Sales 360 Dashboard";
SetupChartBounds(UIApplication.SharedApplication.StatusBarOrientation);
}
public void SetupCharts()
{
SetupMeetingsChartHeaderBar();
SetupTermsOfBusinessChartHeaderBar();
SetupIllustrationsChartHeaderBar();
SetupSystemUserChartHeaderBar();
SetupFirmDataChartHeaderBar();
this.AddSubview(_homeViewModel.MeetingsChart);
this.AddSubview(_homeViewModel.TermsOfBusinessChart);
this.AddSubview(_homeViewModel.IllustrationsChart);
this.AddSubview(_homeViewModel.SystemUseChart);
this.AddSubview(_homeViewModel.FirmDataChart);
}
}
I'd really appreciate any help on this problem as I'm badly stuck on it. Thanks.
EDIT 1:
The HomeController is pushed from something called the SplashController. The only purpose of the SplashController is to display a splash screen view and call web services. The web services are called asynchronously and when completed will change the view to the home view where all charts are displayed.
Here is some of the SplashController class:
public class SplashController : UIViewController
{
private SplashView _splashView;
private SplashViewModel _splashViewModel;
private readonly string BACKGROUND_IMAGE_PATH;
private HomeController _homeController;
.....
public override void LoadView()
{
LoadViewModel();
_splashView = new SplashView(new RectangleF(0, 0, Dimensions.Width, Dimensions.Height), _splashViewModel);
this.View = _splashView;
}
// .... a number of service calls eventually leading to this
protected void GetTermsOfBusinessAllAgentsCompleted(object sender, GetTermsOfBusinessAllAgentsCompletedEventArgs e)
{
_servicesHelper.StopTimer(e.UserState as Timer);
if (e.Error != null)
HandleError(e.Error, "Terms of Business");
else
{
_termsOfBusinessGraphData = e.Result;
ChangeViewToHomeView();
}
}
private void ChangeViewToHomeView()
{
_homeController = new HomeController(_meetingsGraphData, _firmDataGraphData, _systemUseGraphData,
_illustrationsGraphData, _termsOfBusinessGraphData);
this.NavigationController.PushViewController(_homeController, false);
}
}
The HomeView is created in the LoadView method overridden in the HomeController class and basically looks like this:
_homeView = new HomeView(new RectangleF(0, 0, Dimensions.Width, Dimensions.Height), this.NavigationController, _homeViewModel);
this.View = _homeView;
As for the NavigationController, good question. This was something I was doing earlier where I was passing a NavigationController to a delegate. However, now all of this is done in the HomeController so it is no longer needed. Sorry about that, my mistake! I have now taken this out and tried re-running the app, the same thing still happens. Please ignore this bit.
EDIT 2:
Here is the delegate for one of the charts. I've missed out the methods which handles errors as it's not needed. The _meetingServices.GetAgentsData method calls a web service in that method which then returns to the event in this object:
public class DashboardMeetingChartDelegate : SChartDelegate
{
private UINavigationController _navigationController;
private CategoryGraph _meetingsGraphData;
private MeetingServices _meetingServices;
private ServicesHelper _servicesHelper;
private int _currentIndex;
public DashboardMeetingChartDelegate(UINavigationController navigationController, CategoryGraph meetingsGraphData)
{
this._navigationController = navigationController;
this._meetingsGraphData = meetingsGraphData;
_meetingServices = new MeetingServices();
_servicesHelper = new ServicesHelper();
_currentIndex = -1;
}
protected override void OnToggledSelection (ShinobiChart chart, SChartDataPoint dataPoint, SChartSeries series, PointF pixelPoint)
{
_currentIndex = dataPoint.Index;
_meetingServices.GetAgentsData(GetAgentsCompleted);
}
protected void GetAgentsCompleted(object sender, GetAgentsCompletedEventArgs e)
{
_servicesHelper.StopTimer(e.UserState as Timer);
if (e.Error != null)
HandleError(e.Error);
else
{
int rsmId = Convert.ToInt32(_meetingsGraphData.Data[0].SeriesDataPoints[_currentIndex].PointMetaData);
AgentContract currentAgent = e.Result.Agents.Where(agent => agent.Id == rsmId).First();
_navigationController.PushViewController(new MeetingController(currentAgent), true);
}
}
And here is the MeetingController - pretty similar to the other one except there's less to load:
public class MeetingController : UIViewController
{
private AgentContract _currentAgent;
private RsmChartView _meetingView;
private RsmChartViewModel _meetingViewModel;
private MeetingTypeController _meetingTypeController;
private ColumnChartDataProvider _columnChartDataProvider;
public MeetingController(AgentContract currentAgent)
{
_currentAgent = currentAgent;
}
public override void LoadView ()
{
LoadColumnChartDataProvider();
LoadViewModel();
InitialiseView();
}
private void LoadColumnChartDataProvider()
{
_columnChartDataProvider = new ColumnChartDataProvider();
_columnChartDataProvider.XValueList.Add(new NSString("Phone"));
_columnChartDataProvider.XValueList.Add(new NSString("Demo"));
_columnChartDataProvider.XValueList.Add(new NSString("Fact Finding"));
_columnChartDataProvider.YValueList.Add(61);
_columnChartDataProvider.YValueList.Add(22);
_columnChartDataProvider.YValueList.Add(27);
}
private void LoadViewModel()
{
_meetingViewModel = new RsmChartViewModel();
_meetingViewModel.Chart = LoadMeetingChart();
_meetingViewModel.RsmContentModel = LoadRsmContentModel();
}
private RsmContentModel LoadRsmContentModel()
{
RsmContentModel model = new RsmContentModel();
model.Name = _currentAgent.Name;
return model;
}
private void InitialiseView()
{
_meetingView = new RsmChartView(new RectangleF(0, 0, Dimensions.Width, Dimensions.Height), _meetingViewModel);
this.View = _meetingView;
}
private ColumnChart LoadMeetingChart()
{
ColumnChart meetingChart = new ColumnChart(new RectangleF(10, 220, Dimensions.Width - 20, 540));
_meetingTypeController = new MeetingTypeController();
_meetingViewModel.ChartDataSource = new ColumnChartDataSource(_columnChartDataProvider);
_meetingViewModel.ChartDelegate = new ColumnChartDelegate(this.NavigationController, _meetingTypeController);
meetingChart.DataSource = _meetingViewModel.ChartDataSource;
meetingChart.Delegate = _meetingViewModel.ChartDelegate;
return meetingChart;
}
public override void ViewDidLoad()
{
_meetingView.Load();
}
}
Upvotes: 3
Views: 317
Reputation: 43553
This condition is often hard to debug. Some changes have been made to MonoTouch to reduce such occurrences.
To try them I suggest you to try 6.0.7 (on the beta channel) and report any outstanding issue on the mailing list (or a bug report).
More details can be found in the release notes (direct link).
Upvotes: 3