Repository navigation
Conversation
Laying out a tree on iOS spent most of its time marshalling between JS and UIKit rather than in UIKit itself. Containers (layouts, ScrollView, Page content) default to iosOverflowSafeArea, so every time a container's frame changed, expandBeyondSafeArea built four CGRects (the safe area, the full area, the frame in the window, the frame) and read each one back through getPositionFromFrame: about 70 native getter calls per view, for a result that is the frame itself unless the view touches the edge of the safe area. - expandBeyondSafeArea reads each native value once (the reference's insets, content size and offset or safe-area guide, the view's location in the window, the frame) and computes the positions in JS with the same arithmetic, so the result is identical. When nothing is expanded and the frame is already on device pixels, it returns the frame itself, and the frame is not set a second time. - layout() uses the position of a frame it has just set, unadjusted and untransformed, instead of reading the native frame back. - layoutNativeView() skips the native calls when the position is the one it last laid out, which _setNativeViewFrame would leave alone anyway. - getSafeAreaInsets() checks iosIgnoreSafeArea and the managed-inset ancestor before reading the native insets. - measureNativeView() and Label's text measure return plain objects instead of writing into the CGSize, which goes through native struct setters. On the xplat-benchmarks ns-core app (iPhone 17 Pro Max simulator, size M, 3 interleaved rounds), resizing a 1,093-view tree went from 148 ms to 81 ms (grow) and 118 ms to 65 ms (shrink), restyling it from 174 ms to 103 ms, and mounting it from 250 ms to 181 ms.
|
View your CI Pipeline Execution ↗ for commit b24f3e8
💡 Verify your cache is correct by running tasks in a sandbox. Read docs ↗ ☁️ Nx Cloud last updated this comment at |
commit: |
| right, | ||
| bottom, | ||
| }); | ||
| this._pendingFramePosition = IOSHelper.getPositionFromFramePosition(left, top, right, bottom); |
There was a problem hiding this comment.
isnt it a duplicate cost as you already go through parents in getFrameFromPosition? Cant we get position from frame?
| // get the frame and adjust the position, so that onLayout works correctly | ||
| position = IOSHelper.getPositionFromFrame(this.nativeViewProtected.frame); | ||
| // get the frame and adjust the position, so that onLayout works correctly. | ||
| // A frame set here without adjustment is known without reading it back |
There was a problem hiding this comment.
Those AI comments are too long. In the end it will pollute the code. We should keep them small
| if (IOSHelper.hasIOSManagedInsetAncestor(this)) { | ||
| return insets; | ||
| } | ||
| const safeAreaInsets = this.nativeViewProtected && this.nativeViewProtected.safeAreaInsets; |
There was a problem hiding this comment.
const safeAreaInsets = this.nativeViewProtected?.safeAreaInsets
|
A few of these optimizations might not be needed anymore since accessing structs is now a few ns. Some paths had 20x speedup so what you're doing might even have become suboptimal now under jitless |
|
@edusperoni nice I'll rerun the benchmarks to see new numbers. |
PR Checklist
What is the current behavior?
Laying out a tree on iOS spends most of its time marshalling between JS and UIKit rather than in UIKit. Containers (layouts, ScrollView, Page content) default to
iosOverflowSafeArea, so every time a container's frame changes,IOSHelper.expandBeyondSafeAreabuilds fourCGRects (the safe area, the full area, the frame in the window, the frame) and reads each one back throughgetPositionFromFrame: about 70 native getter and struct calls per view, for a result that is the frame itself unless the view touches the edge of the safe area.Instrumenting core's layout methods in the xplat-benchmarks
ns-coreapp (iOS simulator, a 1,093-view tree) showed each resize callinggetPositionFromFrameabout 5 times per view, andapplySafeAreaInsetstaking more time than all the measuring.What is the new behavior?
The results are the same; the native calls are fewer.
expandBeyondSafeAreareads each native value once (the reference scroll view's insets, content size and offset, or the controller's safe-area guide and frame; the view's location in the window; the frame) and computes the positions in JS with the same arithmetic. When nothing is expanded and the frame is already on device pixels, it returns the frame itself, and_modifyNativeViewFramedoesn't set it a second time.getAvailableSpaceFromParentis unchanged for other callers.layout()uses the position of a frame it has just set, unadjusted and untransformed, instead of reading the native frame back. A transformed view's frame is still read, since it is its transformed bounding box.layoutNativeView()skips the native calls when the position is the one it last laid out, which_setNativeViewFramewould leave alone anyway.getSafeAreaInsets()checksiosIgnoreSafeAreaand the managed-inset ancestor before reading the native insets.layout.measureNativeView()and Label's text measure return plain objects instead of writing into theCGSize, which goes through native struct setters.Performance
xplat-benchmarks
ns-coreapp (plain core layouts, no plugins), iPhone 17 Pro Max simulator (iOS 26.4), size M, 3 interleaved rounds of 10 iterations, median ms until painted. To reproduce, pointapps/ns-coreat each build of core, build it withscripts/build-ios.sh <simulator-udid> ns-core, and runnpm run bench -- run --platform ios --device <udid> --apps ns-core --scenarios mount,relayout --sizes M --rounds 3(see the repo's README):Tests
view-helper/safe-area-expansion.spec.tsruns the newexpandBeyondSafeAreaand the previous computation (getAvailableSpaceFromParent+getPositionFromFrame) on 2,000 generated frames inside a scroll view and a view controller, with notched insets and fractional positions, and requires identical rects (over 200 of them expanded).view-helper/frame-position.spec.tschecks the JS position against reading it back from the frame.apps/automatedpasses on the iOS 26.4 simulator (1827 OK, 0 failed), including the 137 SAFEAREA layout, list view, scroll view, repeater and web view tests. The source changes are iOS-only (.ios.tsfiles, plus two declarations inview-helper/index.d.ts).