WatchOS:是否应该在主线程上调用来自扩展的 UI 更新?
WatchOS: Should UI updates from the extension be called on the main thread?
对于 iOS 个应用程序,UI 更新仅从主线程完成 - 不建议这样做,否则会导致意外行为。
在 watchOS 中,OS 由 watch 扩展和应用组成 - 与 'containers' 不同。通常,UI 更新是从扩展中调用的,这些更新会更新应用程序容器中的某些内容。
相同的主线程逻辑是否适用于从手表扩展更新 UI,或者可以从后台调用 UI 更新?
编辑 - 使内容更加清晰。从应用程序的容器中,UI 更新可能应该发生在主线程上(就像大多数 systems/OSs 中发生的那样,如下所述)。真正的问题是 watchOS 是否为我们处理了这个问题,即在扩展的后台线程上调用 UI 更新是否会自动为我们发布到应用程序容器的主线程。
您应该始终在主线程上进行 UI 更新。不这样做会导致 UI 渲染速度变慢或潜在的应用程序崩溃。这并非特定于 iOS 或 watchOS,因为几乎每种编程语言(C#、Java、C++ 等)都要求您在主线程上进行 UI 更新。
在 watchOS 1 中,您的建议可能有意义,因为扩展在 iPhone 上并且 UI 在手表上。在那种情况下,应用程序将 运行 作为两个单独的进程执行,并且您 "Might" 不需要分派到主线程以进行 UI 更新。但在 watchOS 2 中就不一样了。即使认为 watchOS 扩展和 UI 具有不同的目标,在 watchOS 2 中它们也不会 运行 作为手表上的单独进程(您可以通过查看 apple watch 上的 运行ning 进程来验证这一点在 Xcode 中并看到每个应用程序只有一个)。仅仅因为它被打包为两个独立的容器(甚至签名不同)并不意味着它们 运行 作为手表上的两个独立进程。
Apple 的 App Programming Guide for watchOS 可能是权威指南,但我在那里找不到关于在主线程以外的线程上进行 UI 更新的参考资料。
人们会认为,如果从主线程调用 UI 更新很重要,那么它会在某处明确声明(就像在 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.
尽管如此,上述引用也可以解释为 UI 对 Watch 扩展的更新也是如此,因为那是 iOS 上的 运行。
综上所述,我认为没有任何 Apple 文档说明其中一种方式。
这里还有另一个数据点:Apple 的 Lister sample code 现在包含一个 WatchKit 扩展,根据我对它的简要研究,它似乎正在将提取分派到后台队列(参见 ListInfo.swift: 34) 并通过分派回主队列来更新 UI (ListsInterfaceController.swift: 98)。那里甚至有评论说它正在这样做:
The fetchInfoWithCompletionHandler(_:) method calls its completion handler on a background
queue, dispatch back to the main queue to make UI updates.
我认为基于上述情况,我宁愿在主线程上进行更新,除非您确定这样做对性能或其他影响。
通过技术支持事件联系 Apple 后,收到的答复和解释如下。
TLDR: 使用主线程。
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.
对于 iOS 个应用程序,UI 更新仅从主线程完成 - 不建议这样做,否则会导致意外行为。
在 watchOS 中,OS 由 watch 扩展和应用组成 - 与 'containers' 不同。通常,UI 更新是从扩展中调用的,这些更新会更新应用程序容器中的某些内容。
相同的主线程逻辑是否适用于从手表扩展更新 UI,或者可以从后台调用 UI 更新?
编辑 - 使内容更加清晰。从应用程序的容器中,UI 更新可能应该发生在主线程上(就像大多数 systems/OSs 中发生的那样,如下所述)。真正的问题是 watchOS 是否为我们处理了这个问题,即在扩展的后台线程上调用 UI 更新是否会自动为我们发布到应用程序容器的主线程。
您应该始终在主线程上进行 UI 更新。不这样做会导致 UI 渲染速度变慢或潜在的应用程序崩溃。这并非特定于 iOS 或 watchOS,因为几乎每种编程语言(C#、Java、C++ 等)都要求您在主线程上进行 UI 更新。
在 watchOS 1 中,您的建议可能有意义,因为扩展在 iPhone 上并且 UI 在手表上。在那种情况下,应用程序将 运行 作为两个单独的进程执行,并且您 "Might" 不需要分派到主线程以进行 UI 更新。但在 watchOS 2 中就不一样了。即使认为 watchOS 扩展和 UI 具有不同的目标,在 watchOS 2 中它们也不会 运行 作为手表上的单独进程(您可以通过查看 apple watch 上的 运行ning 进程来验证这一点在 Xcode 中并看到每个应用程序只有一个)。仅仅因为它被打包为两个独立的容器(甚至签名不同)并不意味着它们 运行 作为手表上的两个独立进程。
Apple 的 App Programming Guide for watchOS 可能是权威指南,但我在那里找不到关于在主线程以外的线程上进行 UI 更新的参考资料。
人们会认为,如果从主线程调用 UI 更新很重要,那么它会在某处明确声明(就像在 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.
尽管如此,上述引用也可以解释为 UI 对 Watch 扩展的更新也是如此,因为那是 iOS 上的 运行。
综上所述,我认为没有任何 Apple 文档说明其中一种方式。
这里还有另一个数据点:Apple 的 Lister sample code 现在包含一个 WatchKit 扩展,根据我对它的简要研究,它似乎正在将提取分派到后台队列(参见 ListInfo.swift: 34) 并通过分派回主队列来更新 UI (ListsInterfaceController.swift: 98)。那里甚至有评论说它正在这样做:
The fetchInfoWithCompletionHandler(_:) method calls its completion handler on a background queue, dispatch back to the main queue to make UI updates.
我认为基于上述情况,我宁愿在主线程上进行更新,除非您确定这样做对性能或其他影响。
通过技术支持事件联系 Apple 后,收到的答复和解释如下。
TLDR: 使用主线程。
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.