|
| 1 | +# Async Rendering in UIKit Integration |
| 2 | + |
| 3 | +Understand how OpenSwiftUI performs asynchronous rendering when hosted in UIKit views. |
| 4 | + |
| 5 | +## Overview |
| 6 | + |
| 7 | +OpenSwiftUI can perform view graph updates and display list rendering on a background thread to improve frame rates and reduce main thread blocking. This async rendering capability is particularly important for smooth animations. |
| 8 | + |
| 9 | +## Architecture |
| 10 | + |
| 11 | +The async rendering pipeline involves several key components: |
| 12 | + |
| 13 | +1. DisplayLink: Manages the render loop and switches between main thread and async thread rendering |
| 14 | +2. ViewGraph: Provides `updateOutputsAsync` to update the view graph on a background thread |
| 15 | +3. UIHostingViewBase: Coordinates the rendering process and manages the display link |
| 16 | +4. ViewRendererHost: Protocol that provides `render` and `renderAsync` methods |
| 17 | + |
| 18 | +## How Async Rendering Works |
| 19 | + |
| 20 | +### The Rendering Flow |
| 21 | + |
| 22 | +When a view needs to update, the following sequence occurs: |
| 23 | + |
| 24 | +1. `DisplayLink` fires on a CADisplayLink callback |
| 25 | +2. `UIHostingViewBase.displayLinkTimer` is called with the current timestamp |
| 26 | +3. If `isAsyncThread` is true, `ViewRendererHost.renderAsync` is invoked |
| 27 | +4. `ViewGraph.updateOutputsAsync` attempts to update outputs on the async thread |
| 28 | +5. If successful, the display list is rendered asynchronously |
| 29 | +6. If async update fails, rendering falls back to the main thread |
| 30 | + |
| 31 | +### The Async Thread |
| 32 | + |
| 33 | +OpenSwiftUI creates a dedicated async rendering thread: |
| 34 | + |
| 35 | + thread.name = "org.OpenSwiftUIProject.OpenSwiftUI.AsyncRenderer" |
| 36 | + thread.qualityOfService = .userInteractive |
| 37 | + |
| 38 | +This thread runs a separate RunLoop and processes display link callbacks when async rendering is enabled. |
| 39 | + |
| 40 | +## Conditions for Async Rendering |
| 41 | + |
| 42 | +Async rendering is only possible when specific conditions are met. The `updateOutputsAsync` method checks: |
| 43 | + |
| 44 | + guard _rootDisplayList.allowsAsyncUpdate(), |
| 45 | + hostPreferenceValues.allowsAsyncUpdate(), |
| 46 | + sizeThatFitsObservers.isEmpty || _rootLayoutComputer.allowsAsyncUpdate() |
| 47 | + else { |
| 48 | + return nil |
| 49 | + } |
| 50 | + |
| 51 | +### Key Requirements |
| 52 | + |
| 53 | +1. hostPreferenceValues must be non-nil: This attribute is set during `instantiateOutputs` when the view contains dynamic containers like `ForEach` or conditional views (`if`/`else`) |
| 54 | + |
| 55 | +2. Attributes must allow async updates: An attribute allows async update when its value state does not contain both `dirty` and `mainThread` flags |
| 56 | + |
| 57 | +3. No pending properties needing update: The host's `propertiesNeedingUpdate` must be empty |
| 58 | + |
| 59 | +4. No pending transactions: The view graph must not have pending transactions |
| 60 | + |
| 61 | +### The hostPreferenceValues Requirement |
| 62 | + |
| 63 | +The `hostPreferenceValues` is set during `ViewGraph.instantiateOutputs`: |
| 64 | + |
| 65 | + hostPreferenceValues = WeakAttribute(outputs.preferences[HostPreferencesKey.self]) |
| 66 | + |
| 67 | +This only works when the view hierarchy contains a `DynamicContainer`. Dynamic containers are created by: |
| 68 | + |
| 69 | +- `ForEach` views |
| 70 | +- Conditional content (`if`/`else` statements) |
| 71 | +- Optional view unwrapping |
| 72 | + |
| 73 | +Without these dynamic elements, `hostPreferenceValues` remains nil and `allowsAsyncUpdate()` returns false. |
| 74 | + |
| 75 | +## Examples |
| 76 | + |
| 77 | +### Views That Support Async Rendering |
| 78 | + |
| 79 | +Views with dynamic content like `ForEach` enable async rendering: |
| 80 | + |
| 81 | + struct AsyncRenderExample: View { |
| 82 | + @State private var items = [6] |
| 83 | + |
| 84 | + var body: some View { |
| 85 | + VStack(spacing: 10) { |
| 86 | + ForEach(items, id: \.self) { item in |
| 87 | + Color.blue.opacity(Double(item) / 6.0) |
| 88 | + .frame(height: 50) |
| 89 | + .transition(.slide) |
| 90 | + } |
| 91 | + } |
| 92 | + .animation(.easeInOut(duration: 2), value: items) |
| 93 | + .onAppear { |
| 94 | + items.removeAll { $0 == 6 } |
| 95 | + } |
| 96 | + } |
| 97 | + } |
| 98 | + |
| 99 | +In this example, the `ForEach` creates a `DynamicContainer`, which sets up the `hostPreferenceValues` attribute, enabling async rendering during the animation. |
| 100 | + |
| 101 | +### Views That Cannot Use Async Rendering |
| 102 | + |
| 103 | +Views without dynamic containers cannot use async rendering: |
| 104 | + |
| 105 | + struct NoAsyncRenderExample: View { |
| 106 | + @State private var showRed = false |
| 107 | + |
| 108 | + var body: some View { |
| 109 | + VStack { |
| 110 | + Color(platformColor: showRed ? .red : .blue) |
| 111 | + .onAppear { |
| 112 | + let animation = Animation.linear(duration: 5) |
| 113 | + .logicallyComplete(after: 1) |
| 114 | + withAnimation(animation, completionCriteria: .logicallyComplete) { |
| 115 | + showRed.toggle() |
| 116 | + } completion: { |
| 117 | + print("Complete") |
| 118 | + } |
| 119 | + } |
| 120 | + } |
| 121 | + } |
| 122 | + } |
| 123 | + |
| 124 | +This view has no `ForEach` or conditional view structure. The color interpolation happens within a single view, so no `DynamicContainer` is created and `hostPreferenceValues` remains nil. |
| 125 | + |
| 126 | +## Debugging Tips |
| 127 | + |
| 128 | +To understand why async rendering is not enabled: |
| 129 | + |
| 130 | +1. Check if your view hierarchy contains `ForEach` or conditional views |
| 131 | +2. Verify that animations are not using completion handlers that require main thread coordination |
| 132 | +3. Use the environment variable `OPENSWIFTUI_PRINT_TREE=1` to inspect the display list structure |
| 133 | + |
| 134 | +## Topics |
| 135 | + |
| 136 | +### Related Types |
| 137 | + |
| 138 | +- ``_UIHostingView`` |
| 139 | +- ``ViewGraph`` |
| 140 | + |
0 commit comments