For consistency sake – be lazy.

If you have done any IOS development, especially if you’re like me and have started using Xamarin for everything, you’ve no doubt ran into the ubiquitous gem:

UIKit Consistency Error: you are calling a UIKit method that can only be invoked from the UI thread.

When I saw this in the logs for GetDealt, I immediately started to chastise myself. How could such a simple error get through the cracks? As I started parsing the stack trace, I noticed something interesting: The executing code was already wrapped in a InvokeOnMainThread() call. Wait, what?
A double check revealed:

this.InvokeOnMainThread(delegate()
{
     //...Code Here...
}); 

If you were sitting next to me when I discovered this, you too would have caused time to freeze as your mind succumbed to pondering the flaws of the universe. Eventually though, time started moving again and the ability to solve the problem returned.

After about 10 minutes of hunting, I found something buried deep within that “Code Here” block. A snippet of code was creating a CALayer and setting its scale to match the devices scale. Necessary? Probably not. Harmless? Absolutely not.

Apparently when you call UIScreen.MainScreen.Scale it does an assertion to make sure you are running on the UIThread. As you can see here:

[Since(, ), CompilerGenerated]
public virtual float Scale
{
	[Export(), Since(, )]
	get
	{
		UIApplication.EnsureUIThread();
		if (this.IsDirectBinding)
		{
			return Messaging.float_objc_msgSend(base.Handle, Selector.GetHandle());
		}
		return Messaging.float_objc_msgSendSuper(base.SuperHandle, Selector.GetHandle());
	}
}

You’ll likely repeat that the code is already running on the UIThread, thus passing the assertion. And I’d immediately agree with you. It “should”. But, for a reason that is still not known to me, it doesn’t. In fact, when you spend a few moments pondering the problem, you’ll likely end up asking the same question I did.

Why do I need the UIThread to get a float that doesn’t change.

That very question, combined with the random freezing of the app, caused me to come up with a simple work-around: Laziness. Instead of asking the api every time it’s needed, why not cache the silly thing and be done with it? I’m not usually known to use lambda’s for property initializers since I believe it promotes laziness, but that’s exactly what we’re aiming for.


private static readonly Lazy<float> _mainScreenScale = new Lazy<float>(() => UIScreen.MainScreen.Scale);
public static float MainScreenScale { get { return _mainScreenScale.Value; } }

With the value being loaded on demand, we only need to ensure that its first use is from the UIThread. All subsequent calls can be from any thread without worrying about consistency. Just in case, you know, you’re already on the UIThread.

Submit a Comment

Your email address will not be published.