Skip to content

Commit b7577c4

Browse files
authored
Thread hop to ensure 2026 platform compatibility (#468)
1 parent f2f8525 commit b7577c4

File tree

2 files changed

+15
-10
lines changed

2 files changed

+15
-10
lines changed

Sources/Introspect.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,14 @@ extension View {
2626
/// - scope: Optionally overrides the view's default scope of introspection.
2727
/// - customize: A closure that hands over the underlying UIKit/AppKit instance ready for customization.
2828
///
29+
/// Note there is no guarantee of one-time execution for this closure. As `customize` may fire multiple times,
30+
/// make sure to guard against repeated or heavy work in your closure by keeping track of its completeness.
31+
///
32+
/// Additionally, note mutating SwiftUI state within `customize` will trigger runtime warnings unless that mutation
33+
/// is wrapped in a `DispatchQueue.main.async { ... }` call. This is because introspect attempts to hand you
34+
/// the requested view as soon as possible, and this might mean SwiftUI isn't ready for state mutations at that
35+
/// particular moment.
36+
///
2937
/// Here's an example usage:
3038
///
3139
/// ```swift

Sources/IntrospectionView.swift

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -121,15 +121,6 @@ struct IntrospectionView<Target: PlatformEntity>: PlatformViewControllerRepresen
121121
customize(target)
122122
controller.handler = nil
123123
}
124-
125-
// - Workaround -
126-
// iOS/tvOS 13 sometimes need a nudge on the next run loop.
127-
if #available(iOS 14, tvOS 14, *) {} else {
128-
DispatchQueue.main.async { [weak controller] in
129-
controller?.handler?()
130-
}
131-
}
132-
133124
return controller
134125
}
135126

@@ -159,7 +150,13 @@ final class IntrospectionPlatformViewController: PlatformViewController {
159150
guard let self else {
160151
return
161152
}
162-
handler?(self)
153+
154+
// NB: .introspect makes no guarantees about the number of times its callback is invoked,
155+
// so the below is fair play to maximize compatibility and predictability
156+
handler?(self) // we call this eagerly as most customization can successfully happen without a thread hop
157+
DispatchQueue.main.async {
158+
handler?(self) // we also thread hop to cover the rest of the cases where the underlying UI component isn't quite ready for customization
159+
}
163160
}
164161
self.isIntrospectionPlatformEntity = true
165162
IntrospectionStore.shared[id, default: .init()].controller = self

0 commit comments

Comments
 (0)