WatchOS: Should UI updates from the extension be called on the main thread? WatchOS: Should UI updates from the extension be called on the main thread? ios ios

WatchOS: Should UI updates from the extension be called on the main thread?


Apple's App Programming Guide for watchOS would probably be the definitive guide, but I can find no reference in there regarding doing UI updates on threads other than the main thread.

One would think that if it was important that UI updates be be called from the main thread, that it would explicitly state that somewhere (as it does in the App Programming Guide for iOS, in the Threads and Concurrency section):

Work involving views, Core Animation, and many other UIKit classes usually must occur on the app’s main thread. There are some exceptions to this rule—for example, image-based manipulations can often occur on background threads—but when in doubt, assume that work needs to happen on the main thread.

Though, the above quote could be construed to be true of to UI updates to a Watch extension also, since that's running on iOS.

All of the above to say, I don't believe there's any Apple documentation stating one way or the other.

Here's another data point, though: Apple's Lister sample code now includes a WatchKit extension, and from my brief studying of it, it appears to be dispatching fetches to a background queue (see ListInfo.swift: 34) and updating the UI by dispatching back to the main queue (ListsInterfaceController.swift: 98). There's even a comment in there saying it's doing that:

The fetchInfoWithCompletionHandler(_:) method calls its completion handler on a background queue, dispatch back to the main queue to make UI updates.

I think based on the above, I'd err on the side of doing updates on the main thread, unless you determine there are performance or other implications of doing so.


After contacting Apple through a Technical Support Incident, the received answer and explanation is below.

TLDR: use the main thread.

All updates should be done from the main thread. This has always been the general recommendation for UIKit and that recommendation extends to watchOS.

It might be helpful to understand the underlying reason for this requirement. Keep in mind that, even with a centralized communication channel to serialize changes, many problems arise when you attempt to manipulate UI state from background threads. For example, while the serialization channel can prevent multiple UI commands from attempting to simultaneously execute, it can’t control the order in which unrelated commands will execute. Consider the following 2 blocks:

block 1 {       DoUIChange1       DoUIChange2     }block 2 {       DoUIChange3       DoUIChange4     }

If both blocks are executed on the main thread, then the actual command stream is either:

DoUIChange1   DoUIChange2   DoUIChange3   DoUIChange4

or…

DoUIChange3   DoUIChange4   DoUIChange1   DoUIChange2

However, if both blocks are executed on their own threads, even more possibilities open up:

DoUIChange3   DoUIChange1   DoUIChange2   DoUIChange4

or..

DoUIChange1   DoUIChange3   DoUIChange2   DoUIChange4

or..

DoUIChange1   DoUIChange3   DoUIChange4   DoUIChange2

etc…

Needless to say, if the UI code is at all complex the number of combinations quickly becomes enormous, making unexpected UI bugs basically unavoidable.


You should always make UI updates on the main thread. Not doing so will result in slower UI rendering or potential app crashes. This is not specific to iOS or watchOS, as pretty much every programming language (C#, Java, C++, etc.) requires that you make UI updates on the main thread.

In watchOS 1, what you are suggesting might make sense since the extension was on the iPhone and the UI was on the watch. In that case the app did run as two separate processes and you "Might" not have needed to dispatch to the main thread for UI updates. But in watchOS 2 it is different. Even thought watchOS extension and UI have different targets, in watchOS 2 they don't run as separate processes on the watch (you can verify this by viewing running processes on your apple watch in Xcode and see that there is only one for each app). Just because it is packaged as two separate containers (and even are signed differently) does not mean that they run as two separate processes on the watch.