UITableView crash, layout engine, didSet

There is plenty written about the woes of updating the UI from a background thread, so I won’t retread that ground.

However, I would like to share what I consider an easy-to-overlook gotcha created by relying on a Swift var’s didSet, specifically in a UIViewController subclass.

I find didSet to be very convenient, and I like the clarity around it. But we have to be careful with view controller objects, because if they are invoking background operations, we can run into some trouble when updating the UI. I instinctively use the below pattern to update my view controllers. There is a collection that is represented in the view controller’s view, and when I update that collection, I want to update my UI as a side effect, which is where didSet comes in to play.

This looks pretty clean and simple, right?

But this will crash, my friend! And you will see this nasty stack trace:

xxxxxxxxxx This application is modifying the autolayout engine from a background thread after the engine was accessed from the main thread. This can lead to engine corruption and weird crashes.
0 CoreFoundation 0x0000000108380d4b __exceptionPreprocess + 171
1 libobjc.A.dylib 0x000000010569321e objc_exception_throw + 48
2 CoreFoundation 0x00000001083ea2b5 +[NSException raise:format:] + 197
3 Foundation 0x000000010538d06c _AssertAutolayoutOnAllowedThreadsOnly + 180
etc. etc.

What happened?

Well, our background operation has a reference to the foodStuffs var, and sets it on its background thread. That’s all well and good, but when foodStuffs is set, it will trigger a side effect: reloading the table view.

Since we’re on a background thread, this reload will be performed on that thread, causing the crash (see bottom note to learn more about testing this).

However, this is an easy problem to solve:

Now we can guarantee that the table view will be reloaded on the main thread, and go about our business.

// You can test this for yourself. Set a var from a background thread
// and log the current thread in scope of the var's didSet:
// should see something like:
// <NSThread: 0x000000000000>{number = 1, name = main}