Accessing the UI in Xamarin.iOS
An interesting discussion happened recently, triggered by a code review of (something like) the following C#:
public class ProductViewController
{
private UIView _detailsView { get; set; }
private ProductViewModel _viewModel { get; set; }
private void Process()
{
if (_viewModel.ProcessDetails)
{
InvokeOnMainThread(() =>
{
if (_detailsView != null)
{
UpdateDetailsView();
}
});
}
}
}
One of the reviewers suggested moving the second if
outside the call to InvokeOnMainThread
(and then combining it with the other if
).
The theory was that it would more efficient to check if we need to be on the main thread before we do it, instead of doing the check on the main thread and finding out we have nothing to do.
The original author pushed back saying you can’t access _detailsView
off the main thread since you would get an exception.
On the surface this sounds reasonable - everyone knows you can only access UIKit objects on the main thread. But it naturally leads to the question: what does “accessing a UIKit object” actually mean?
So I wrote a quick sample that intentionally tries to do lots of UIKit manipulation on background threads in various ways:
The tests
Quick refresher. The
View
property of a newly constructedUIViewController
is not populated until it is first accessed. Some of the tests below refer to accessing this property either before or after it is created. By default properties declared in C# will not be visible to any native iOS code. They can be made visible by adding the[Export]
attribute.
- Create a
UIViewController
. - Create a
UIView
. - Create a
UIColor
. - Check if a view controller’s
View
property is equal tonull
. - Check if a view controller’s
View
property isnull
using pattern matching. - Check if a view controller’s
IsViewLoaded
property is equal totrue
. - Check if a view controller’s
View
property is equal tonull
after previously creatingView
on the main thread. - Check if a view controller’s
View
property isnull
using pattern matching after previously creatingView
on the main thread. - Check if a view controller’s
IsViewLoaded
property is equal totrue
after previously creatingView
on the main thread. - Check if a new
UIView
property, that is not exported is equal tonull
. - Check if a new
UIView
property, that is not exported isnull
using pattern matching. - Check if a new
UIView
property, that is exported is equal tonull
. - Check if a new
UIView
property, that is exported isnull
using pattern matching. - Check if a view controller’s
NavigationController
property is equal tonull
. - Check if a view controller’s
NavigationController
property isnull
using pattern matching. - Set a new
UIView
property, that is not exported, with a view created on the main thread. - Set a new
UIView
property, that is exported, with a view created on the main thread.
Results
Test | Result |
---|---|
Create UIViewController |
Exception |
Create UIView |
Exception |
Create UIColor |
OK |
Check if View is equal to null |
Exception |
Check if View is null pattern |
Exception |
Check if View is loaded |
Exception |
Check if View is equal to null after creating View |
Exception |
Check if View is null pattern after creating View |
Exception |
Check if View is loaded after creating View |
Exception |
Check if non-exported view is equal to null |
OK |
Check if non-exported view is null pattern |
OK |
Check if exported view is equal to null |
OK |
Check if exported view is null pattern |
OK |
Check if NavigationController is equal to null |
Exception |
Check if NavigationController is null pattern |
Exception |
Set non-exported view | OK |
Set exported view | OK |
Summary
This is by no means exhaustive, but it seems in general, acessing properties declared natively in iOS will throw an exception whereas accessing properties you have declared yourself will be fine. I had a suspicion that an exported view might behave the same as a native property, but apparently not.
The code for this test is available on GitHub.