24

For iOS apps, UI updating is done from the main thread exclusively - not doing so is never recommended and can lead to unexpected behaviour.

In watchOS, the OS is structured with a watch extension, and an app - as different 'containers'. Usually, UI updates are called from the extension, and these update something in the app's container.

Does the same main thread logic apply to updating UI from the watch extension, or can UI updates be called from the background?


Edit - to bring some clarity. From the app's container, UI updates should probably happen on the main thread (as happens in most systems/OSs, as pointed out below). The question is really whether watchOS handles that for us, i.e. whether calling a UI update on the background thread of the extension will automatically be posted to the main thread of the app's container for us.

Jordan Smith
  • 10,310
  • 7
  • 68
  • 114
  • Are you asking **should** they be called on the **main thread** or **can** they be called on the **background thread**? – tktsubota Feb 20 '16 at 19:48
  • @TroyT both statements seem equivalent to me, but I guess the 'should' statement is more correct. If something 'shouldn't' be called on the background, I wouldn't do it. Pretty much - "does the same main thread UI logic from iOS apply to watchOS?" – Jordan Smith Feb 21 '16 at 20:36

3 Answers3

4

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.

zpasternack
  • 17,838
  • 2
  • 63
  • 81
  • Great, that's some more solid logic there - unfortunately, two things that I'm not 100% convinced on: as of watchOS 2, the extension no longer runs on iOS, but on watchOS - that counters your rule of following iOS logic. Secondly - I believe the lister sample code uses that call on more than just the watch extension, which is why it necessitates calls back to the main thread - the code could be running as part of the iOS app instead. I'll hold off on giving the bounty just yet. – Jordan Smith Feb 24 '16 at 04:26
  • Your point of erring on the side of caution, for now, is the only thing I'm fully convinced on! Thanks for the answer though. As to the reason I'd like to know - I'd just rather not have 50 calls back to the main thread unnecessarily. It might make the UI update happen one runloop later, not a big deal but every bit of responsiveness counts. – Jordan Smith Feb 24 '16 at 04:30
  • given your answer was auto selected for the bounty, I think you should probably edit it to take away any incorrect statements. I'd be happy to temporarily mark it as the correct answer until something more certain is found. – Jordan Smith Feb 28 '16 at 08:29
2

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.

Jordan Smith
  • 10,310
  • 7
  • 68
  • 114
-1

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.

lehn0058
  • 19,977
  • 15
  • 69
  • 109
  • I hear what you're saying, but I don't agree/wouldn't say that this is an acceptable answer. For one, the watch app and extension live in completely different containers. They hardly know anything about the other. How do you know they are the same process? – Jordan Smith Feb 23 '16 at 21:21
  • When you call a UI update from a background extension thread, how do you know that watchOS doesn't call a main thread UI update in the app's container, respectively? The question is not really whether UI updating in general should be done on the main thread, that's a given. Rather - from the 'extension', should it be done on the main thread - or is that handled for us, and can we call from a background thread without needing to jump back onto main for every UI update? – Jordan Smith Feb 23 '16 at 21:23
  • First, jumping between processes would add a noticeable time delay. Second, you can attach Xcode to the running processes on your apple watch when your iPhone is plugged in and the watch is reachable (Debug -> Attach To Process). You will notice that only the extension process is available to attach to. Third, I have encountered numerous UI bugs and crashes on watchOS because of not returning to the UI thread after doing an async call (mostly health kit queries). – lehn0058 Feb 23 '16 at 21:52
  • Ok, thanks for clarity. I still don't agree with you on a couple of points though sorry, let me explain. Just because you can't attach the debugger to the app container process doesn't mean that it's all called on the same thread you use in the extension. For all you know, there's some system process that loads the watchOS app container and the WatchKit extension API is passing UI calls through to this. – Jordan Smith Feb 23 '16 at 23:01
  • About the UI async bugs - I'm not convinced sorry. I'm currently doing a few in my watchOS app and it 'seems' to be working fine. Your crashes caused by not returning to the main thread could quite likely be a Core Data threading issue, or some other sort of threading issue, not related to updating the UI. There are many of these sorts of threading issues I see in projects I get to work on, Core Data being the most prominent - not always UI related. – Jordan Smith Feb 23 '16 at 23:04
  • Sorry if I seem a bit harsh. I'm looking for a definitive answer, as the stuff I'm working on is used by a lot of people - pays to get it right. Your answer is just missing the solid evidence that I think is necessary for a 'definitive' answer, at this stage there's plenty of reasons I can think of why the answer could be proved incorrect. If you can provide solid evidence or documentation though then I'd be happy to accept your answer. – Jordan Smith Feb 23 '16 at 23:09