Skip to content

Commit a5a1d7c

Browse files
authored
Optimize receiver lookup algorithm (#246)
1 parent c29b50a commit a5a1d7c

File tree

5 files changed

+117
-101
lines changed

5 files changed

+117
-101
lines changed

Sources/Introspect.swift

Lines changed: 15 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,18 @@ extension View {
2020
customize: @escaping (PlatformSpecificEntity) -> Void
2121
) -> some View {
2222
if let platform = platforms.first(where: \.isCurrent) {
23-
let anchorID = IntrospectionAnchorID()
23+
let introspectionViewID = IntrospectionViewID()
2424
self.background(
2525
IntrospectionAnchorView(
26-
id: anchorID
26+
id: introspectionViewID
2727
)
2828
.frame(width: 0, height: 0)
2929
)
3030
.overlay(
3131
IntrospectionView(
32-
selector: { entity in
33-
(platform.selector ?? .default)(entity, scope ?? viewType.scope, anchorID)
32+
id: introspectionViewID,
33+
selector: { controller in
34+
(platform.selector ?? .default)(controller, scope ?? viewType.scope)
3435
},
3536
customize: customize
3637
)
@@ -53,9 +54,6 @@ public protocol PlatformEntity: AnyObject {
5354

5455
@_spi(Internals)
5556
func isDescendant(of other: Base) -> Bool
56-
57-
@_spi(Internals)
58-
func entityWithTag(_ tag: Int) -> Base?
5957
}
6058

6159
extension PlatformEntity {
@@ -65,8 +63,8 @@ extension PlatformEntity {
6563
}
6664

6765
@_spi(Internals)
68-
public var allDescendants: [Base] {
69-
self.descendants.reduce([self~]) { $0 + $1.allDescendants~ }
66+
public var allDescendants: some Sequence<Base> {
67+
recursiveSequence([self~], children: { $0.descendants~ }).dropFirst()
7068
}
7169

7270
func nearestCommonAncestor(with other: Base) -> Base? {
@@ -79,37 +77,26 @@ extension PlatformEntity {
7977
return nearestAncestor
8078
}
8179

82-
func descendantsBetween(_ bottomEntity: Base, and topEntity: Base) -> [Base] {
83-
var result: [Base] = []
84-
var entered = false
85-
86-
for descendant in self.allDescendants {
87-
if descendant === bottomEntity {
88-
entered = true
89-
} else if descendant === topEntity {
90-
break
91-
} else if entered {
92-
result.append(descendant)
93-
}
94-
}
95-
96-
return result
80+
func allDescendants(between bottomEntity: Base, and topEntity: Base) -> some Sequence<Base> {
81+
self.allDescendants
82+
.lazy
83+
.drop(while: { $0 !== bottomEntity })
84+
.prefix(while: { $0 !== topEntity })
9785
}
9886

9987
func receiver<PlatformSpecificEntity: PlatformEntity>(
100-
ofType type: PlatformSpecificEntity.Type,
101-
anchorID: IntrospectionAnchorID
88+
ofType type: PlatformSpecificEntity.Type
10289
) -> PlatformSpecificEntity? {
10390
let frontEntity = self
10491
guard
105-
let backEntity = Array(frontEntity.ancestors).last?.entityWithTag(anchorID.hashValue),
92+
let backEntity = frontEntity.introspectionAnchorEntity,
10693
let commonAncestor = backEntity.nearestCommonAncestor(with: frontEntity~)
10794
else {
10895
return nil
10996
}
11097

11198
return commonAncestor
112-
.descendantsBetween(backEntity~, and: frontEntity~)
99+
.allDescendants(between: backEntity~, and: frontEntity~)
113100
.compactMap { $0 as? PlatformSpecificEntity }
114101
.first
115102
}
@@ -134,11 +121,6 @@ extension PlatformView: PlatformEntity {
134121
public var descendants: [PlatformView] {
135122
subviews
136123
}
137-
138-
@_spi(Internals)
139-
public func entityWithTag(_ tag: Int) -> PlatformView? {
140-
viewWithTag(tag)
141-
}
142124
}
143125

144126
extension PlatformViewController: PlatformEntity {
@@ -156,17 +138,4 @@ extension PlatformViewController: PlatformEntity {
156138
public func isDescendant(of other: PlatformViewController) -> Bool {
157139
self.ancestors.contains(other)
158140
}
159-
160-
@_spi(Internals)
161-
public func entityWithTag(_ tag: Int) -> PlatformViewController? {
162-
if self.view.tag == tag {
163-
return self
164-
}
165-
for child in children {
166-
if let childWithTag = child.entityWithTag(tag) {
167-
return childWithTag
168-
}
169-
}
170-
return nil
171-
}
172141
}

Sources/IntrospectionSelector.swift

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,28 +6,28 @@ public struct IntrospectionSelector<Target: PlatformEntity> {
66
@_spi(Internals)
77
public static func from<Entry: PlatformEntity>(_ entryType: Entry.Type, selector: @escaping (Entry) -> Target?) -> Self {
88
.init(
9-
receiverSelector: { controller, anchorID in
10-
controller.as(Entry.self)?.receiver(ofType: Entry.self, anchorID: anchorID).flatMap(selector)
9+
receiverSelector: { controller in
10+
controller.as(Entry.Base.self)?.receiver(ofType: Entry.self).flatMap(selector)
1111
},
1212
ancestorSelector: { controller in
13-
controller.as(Entry.self)?.ancestor(ofType: Entry.self).flatMap(selector)
13+
controller.as(Entry.Base.self)?.ancestor(ofType: Entry.self).flatMap(selector)
1414
}
1515
)
1616
}
1717

18-
private var receiverSelector: (IntrospectionPlatformViewController, IntrospectionAnchorID) -> Target?
18+
private var receiverSelector: (IntrospectionPlatformViewController) -> Target?
1919
private var ancestorSelector: (IntrospectionPlatformViewController) -> Target?
2020

2121
private init(
22-
receiverSelector: @escaping (IntrospectionPlatformViewController, IntrospectionAnchorID) -> Target?,
22+
receiverSelector: @escaping (IntrospectionPlatformViewController) -> Target?,
2323
ancestorSelector: @escaping (IntrospectionPlatformViewController) -> Target?
2424
) {
2525
self.receiverSelector = receiverSelector
2626
self.ancestorSelector = ancestorSelector
2727
}
2828

2929
@_spi(Internals)
30-
public func withReceiverSelector(_ selector: @escaping (PlatformViewController, IntrospectionAnchorID) -> Target?) -> Self {
30+
public func withReceiverSelector(_ selector: @escaping (PlatformViewController) -> Target?) -> Self {
3131
var copy = self
3232
copy.receiverSelector = selector
3333
return copy
@@ -40,14 +40,10 @@ public struct IntrospectionSelector<Target: PlatformEntity> {
4040
return copy
4141
}
4242

43-
func callAsFunction(
44-
_ controller: IntrospectionPlatformViewController,
45-
_ scope: IntrospectionScope,
46-
_ anchorID: IntrospectionAnchorID
47-
) -> Target? {
43+
func callAsFunction(_ controller: IntrospectionPlatformViewController, _ scope: IntrospectionScope) -> Target? {
4844
if
4945
scope.contains(.receiver),
50-
let target = receiverSelector(controller, anchorID)
46+
let target = receiverSelector(controller)
5147
{
5248
return target
5349
}
@@ -62,14 +58,14 @@ public struct IntrospectionSelector<Target: PlatformEntity> {
6258
}
6359

6460
extension PlatformViewController {
65-
func `as`<Entity: PlatformEntity>(_ entityType: Entity.Type) -> (any PlatformEntity)? {
66-
if Entity.Base.self == PlatformView.self {
61+
func `as`<Base: PlatformEntity>(_ baseType: Base.Type) -> (any PlatformEntity)? {
62+
if Base.self == PlatformView.self {
6763
#if canImport(UIKit)
6864
return viewIfLoaded
6965
#elseif canImport(AppKit)
7066
return isViewLoaded ? view : nil
7167
#endif
72-
} else if Entity.Base.self == PlatformViewController.self {
68+
} else if Base.self == PlatformViewController.self {
7369
return self
7470
}
7571
return nil

Sources/IntrospectionView.swift

Lines changed: 63 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,30 @@
11
import SwiftUI
22

3-
@_spi(Internals)
4-
public typealias IntrospectionAnchorID = UUID
3+
typealias IntrospectionViewID = UUID
4+
5+
fileprivate enum IntrospectionStore {
6+
static var shared: [IntrospectionViewID: Pair] = [:]
7+
8+
struct Pair {
9+
weak var controller: IntrospectionPlatformViewController?
10+
weak var anchor: IntrospectionAnchorPlatformViewController?
11+
}
12+
}
13+
14+
extension PlatformEntity {
15+
var introspectionAnchorEntity: Base? {
16+
if let introspectionController = self as? IntrospectionPlatformViewController {
17+
return IntrospectionStore.shared[introspectionController.id]?.anchor~
18+
}
19+
if
20+
let view = self as? PlatformView,
21+
let introspectionController = view.introspectionController
22+
{
23+
return IntrospectionStore.shared[introspectionController.id]?.anchor?.view~
24+
}
25+
return nil
26+
}
27+
}
528

629
/// ⚓️
730
struct IntrospectionAnchorView: PlatformViewControllerRepresentable {
@@ -14,9 +37,9 @@ struct IntrospectionAnchorView: PlatformViewControllerRepresentable {
1437
@Binding
1538
private var observed: Void // workaround for state changes not triggering view updates
1639

17-
let id: IntrospectionAnchorID
40+
let id: IntrospectionViewID
1841

19-
init(id: IntrospectionAnchorID) {
42+
init(id: IntrospectionViewID) {
2043
self._observed = .constant(())
2144
self.id = id
2245
}
@@ -31,36 +54,19 @@ struct IntrospectionAnchorView: PlatformViewControllerRepresentable {
3154
}
3255

3356
final class IntrospectionAnchorPlatformViewController: PlatformViewController {
34-
let id: IntrospectionAnchorID
35-
36-
init(id: IntrospectionAnchorID) {
37-
self.id = id
57+
init(id: IntrospectionViewID) {
3858
super.init(nibName: nil, bundle: nil)
59+
IntrospectionStore.shared[id, default: .init()].anchor = self
3960
}
4061

4162
@available(*, unavailable)
4263
required init?(coder: NSCoder) {
4364
fatalError("init(coder:) has not been implemented")
4465
}
4566

46-
#if canImport(UIKit)
47-
override func viewDidLoad() {
48-
super.viewDidLoad()
49-
view.tag = id.hashValue
50-
}
51-
#elseif canImport(AppKit)
52-
final class TaggableView: NSView {
53-
private var _tag: Int?
54-
override var tag: Int {
55-
get { _tag ?? super.tag }
56-
set { _tag = newValue }
57-
}
58-
}
59-
67+
#if canImport(AppKit) && !targetEnvironment(macCatalyst)
6068
override func loadView() {
61-
let view = TaggableView()
62-
view.tag = id.hashValue
63-
self.view = view
69+
view = NSView()
6470
}
6571
#endif
6672
}
@@ -78,14 +84,17 @@ struct IntrospectionView<Target: PlatformEntity>: PlatformViewControllerRepresen
7884

7985
@Binding
8086
private var observed: Void // workaround for state changes not triggering view updates
87+
private let id: IntrospectionViewID
8188
private let selector: (IntrospectionPlatformViewController) -> Target?
8289
private let customize: (Target) -> Void
8390

8491
init(
92+
id: IntrospectionViewID,
8593
selector: @escaping (IntrospectionPlatformViewController) -> Target?,
8694
customize: @escaping (Target) -> Void
8795
) {
8896
self._observed = .constant(())
97+
self.id = id
8998
self.selector = selector
9099
self.customize = customize
91100
}
@@ -95,7 +104,7 @@ struct IntrospectionView<Target: PlatformEntity>: PlatformViewControllerRepresen
95104
}
96105

97106
func makePlatformViewController(context: Context) -> IntrospectionPlatformViewController {
98-
let controller = IntrospectionPlatformViewController { controller in
107+
let controller = IntrospectionPlatformViewController(id: id) { controller in
99108
guard let target = selector(controller) else {
100109
return
101110
}
@@ -128,16 +137,22 @@ struct IntrospectionView<Target: PlatformEntity>: PlatformViewControllerRepresen
128137
}
129138

130139
final class IntrospectionPlatformViewController: PlatformViewController {
140+
let id: IntrospectionViewID
131141
var handler: (() -> Void)? = nil
132142

133-
fileprivate init(handler: ((IntrospectionPlatformViewController) -> Void)?) {
143+
fileprivate init(
144+
id: IntrospectionViewID,
145+
handler: ((IntrospectionPlatformViewController) -> Void)?
146+
) {
147+
self.id = id
134148
super.init(nibName: nil, bundle: nil)
135149
self.handler = { [weak self] in
136150
guard let self = self else {
137151
return
138152
}
139153
handler?(self)
140154
}
155+
IntrospectionStore.shared[id, default: .init()].controller = self
141156
}
142157

143158
@available(*, unavailable)
@@ -146,13 +161,14 @@ final class IntrospectionPlatformViewController: PlatformViewController {
146161
}
147162

148163
#if canImport(UIKit)
149-
override func didMove(toParent parent: UIViewController?) {
150-
super.didMove(toParent: parent)
164+
override func viewDidLoad() {
165+
super.viewDidLoad()
166+
view.introspectionController = self
151167
handler?()
152168
}
153169

154-
override func viewDidLoad() {
155-
super.viewDidLoad()
170+
override func didMove(toParent parent: UIViewController?) {
171+
super.didMove(toParent: parent)
156172
handler?()
157173
}
158174

@@ -168,6 +184,7 @@ final class IntrospectionPlatformViewController: PlatformViewController {
168184
#elseif canImport(AppKit)
169185
override func loadView() {
170186
view = NSView()
187+
view.introspectionController = self
171188
}
172189

173190
override func viewDidLoad() {
@@ -181,3 +198,18 @@ final class IntrospectionPlatformViewController: PlatformViewController {
181198
}
182199
#endif
183200
}
201+
202+
import ObjectiveC
203+
204+
extension PlatformView {
205+
fileprivate var introspectionController: IntrospectionPlatformViewController? {
206+
get {
207+
let key = unsafeBitCast(Selector(#function), to: UnsafeRawPointer.self)
208+
return objc_getAssociatedObject(self, key) as? IntrospectionPlatformViewController
209+
}
210+
set {
211+
let key = unsafeBitCast(Selector(#function), to: UnsafeRawPointer.self)
212+
objc_setAssociatedObject(self, key, newValue, .OBJC_ASSOCIATION_ASSIGN)
213+
}
214+
}
215+
}

0 commit comments

Comments
 (0)