WWDC25: Optimize SwiftUI performance with Instruments | Apple

WWDC25: Optimize SwiftUI performance with Instruments | Apple

Performance Optimization in SwiftUI Apps

Introduction to Performance Issues

  • Jed and Steven introduce themselves, emphasizing the importance of app performance and identifying code bottlenecks.
  • Symptoms of performance issues include unresponsiveness, animation stutters, and delayed scrolling. Profiling with Instruments is recommended for diagnosis.

Focus on SwiftUI Performance

  • The session will cover diagnosing performance issues specifically in SwiftUI applications using a new instrument included in Instruments 26.
  • Acknowledgment that various causes exist for performance problems; however, the focus will be on those related to SwiftUI.

Overview of the Landmarks App

  • Steven introduces the Landmarks app, which showcases global landmarks and their distances from the user's location.
  • Noted that while testing, scrolling isn't as smooth as desired; this prompts further investigation into performance.

New SwiftUI Instrument Features

  • Introduction of a next-generation SwiftUI instrument within Instruments 26 designed to identify performance issues effectively.
  • The instrument includes several tools:
  • SwiftUI Instrument: Tracks when work is done by SwiftUI.
  • Time Profiler: Monitors CPU workload over time.
  • Hangs and Hitches instruments: Measures app responsiveness.

Analyzing Performance with Instruments

  • Explanation of how to interpret data from the SwiftUI instrument:
  • "Update Groups" lane indicates when work occurs in SwiftUI.
  • Long updates are categorized into three types: Long View Body Updates, Long Representable Updates, and Other Long Updates.
  • Updates are color-coded (orange/red for problematic ones), guiding users on where to start investigating potential hitches or hangs.

Getting Started with Profiling

  • Instructions provided for installing Xcode 26 and updating devices to support recording SwiftUI traces before profiling an app.

Profiling Process Demonstration

  • Steven demonstrates profiling by launching Instruments through Xcode after compiling the Landmarks app in Release mode.
  • After recording interactions (scrolling through landmarks), he stops recording to analyze profiling data collected during usage.

Inspecting Update Lanes

  • Focus shifts to analyzing long update lanes within the SwiftUI instrument track:
  • Emphasis on inspecting "Long View Body Updates" first due to its commonality in causing performance issues.
  • Detailed examination reveals subtracks for view body updates, representable updates, and other updates—each highlighting long-running processes that need attention.

Understanding SwiftUI Performance: Optimizing View Body Execution

Filtering Long Updates in SwiftUI

  • The speaker demonstrates how to filter updates by selecting the "Long View Body Updates" summary, indicating multiple long updates for investigation.
  • By expanding the app's module and selecting "Show Updates," a sequential list of long updates is revealed, allowing for targeted analysis.

Analyzing CPU Activity with Time Profiler

  • The Time Profiler instrument tracks CPU activity during view body execution, sampling functions at regular intervals to gather performance data.
  • A deep call stack is observed in SwiftUI work, particularly focusing on LandmarkListItemView, which shows significant time spent in a computed property called distance.

Identifying Performance Bottlenecks

  • The distance property is identified as costly due to expensive calls to two formatters: a measurement formatter and a number formatter.
  • These formatters are used every time the view body runs, causing delays in UI updates since they run on the main thread.

Understanding Frame Deadlines and Hitches

  • The speaker explains that even small delays can accumulate, impacting overall performance when many views need updating.
  • A detailed explanation of the render loop on Apple platforms highlights how timely completion of UI updates before frame deadlines is crucial for smooth animations.

Consequences of Delayed View Body Execution

  • If UI updates exceed frame deadlines, it results in visible hitches where previous frames linger longer than intended.
  • Hitches degrade animation fluidity; further resources are suggested for understanding and resolving these issues.

Strategies for Optimizing Distance Calculation

  • To avoid runtime delays from formatting distance strings during view body execution, pre-calculation and caching strategies are proposed.
  • Moving distance string calculations to a centralized location within the LocationFinder class allows for efficient reuse without incurring repeated costs.

Implementing Caching Mechanisms

  • The initializer is updated to create formatters once rather than repeatedly during view body execution. This change enhances performance by reducing overhead associated with creating new instances each time.

Managing Location Updates and Performance in SwiftUI

Implementing Distance Calculation and Caching

  • The speaker discusses adding code to manage updates for landmarks, utilizing an array to store them for distance calculations. A dictionary is used to cache the calculated distance strings.
  • The function updateDistances is introduced, which recalculates distance strings whenever the user's location changes. This function will be called from the view to retrieve cached text.

Optimizing View Body Updates

  • After implementing caching, the speaker notes that long view body updates in LandmarkListItemView have been resolved, improving performance during rendering.
  • Although some long updates remain at the beginning of the trace (during app launch), they do not cause hitches as they occur while building the initial view hierarchy.

Understanding Performance Issues with Unnecessary Updates

  • The speaker emphasizes that while fixing long view body updates enhances performance, excessive unnecessary updates can also lead to issues by delaying frame submissions.
  • A diagram illustrates how many fast updates can collectively miss frame deadlines, causing visual delays or "hitches" during scrolling.

New Feature: Favorite Landmarks

  • The speaker introduces a new feature allowing users to favorite landmarks via a heart button added in LandmarkListItemView, enhancing user engagement with landmark exploration.
  • Code details are shared about how tapping the heart button triggers toggleFavorite, modifying an array of favorite landmarks based on user interaction.

Profiling Performance with Instruments

  • To assess performance impacts from the new feature, the speaker uses Instruments to record interactions while favoriting landmarks like Muir Woods and Mount Fuji.
  • Post-interaction analysis focuses on ensuring that these actions do not trigger unnecessary view updates, highlighting effective debugging practices.

Debugging View Updates in SwiftUI

  • Upon examining update traces for LandmarkListItemView, unexpected multiple updates are noted. The speaker reflects on challenges faced when debugging SwiftUI compared to UIKit apps.
  • A comparison is made between UIKit's backtrace debugging and SwiftUI's recursive update behavior within its framework, indicating complexities unique to SwiftUI development.

This structured summary captures key insights from managing location-based features and optimizing performance within a SwiftUI application context.

Understanding SwiftUI View Updates

The Nature of SwiftUI Updates

  • SwiftUI's declarative nature complicates understanding why views update, as traditional backtraces are ineffective.
  • Views conform to the View protocol and define their appearance through a body property that returns another View value.
  • When added to the view hierarchy, a view receives an attribute from its parent that maintains state throughout its lifetime, despite frequent recreation of view structs.

Attributes and State Management

  • Each view creates attributes for its state variables, tracking changes and defining behavior; this includes creating storage for state variables like isOn.
  • When the body is called, it reads current values from parent views and updates attributes accordingly to reflect any changes in state.
  • Text views depend on environment attributes for styling, which also rely on the updated values from their respective view bodies.

Transaction Handling in SwiftUI

  • Changing a state variable initiates a transaction rather than an immediate update; this transaction marks relevant attributes as outdated.
  • During frame updates, SwiftUI processes transactions sequentially while marking dependencies as outdated without performing additional work initially.
  • Once all dependencies are updated, SwiftUI can determine what needs to be drawn on screen based on the latest attribute states.

Analyzing View Body Execution

  • The question "why did my view body run?" translates to understanding what marked it as outdated; control over dependencies can influence this process.
  • Recognizing when updates occur is crucial; tools like the new SwiftUI instrument help visualize cause-and-effect relationships in updates.

Cause & Effect Graph Insights

  • The Cause & Effect Graph illustrates relationships between state changes and view updates with nodes representing actions leading to updates.
  • Selecting nodes reveals backtraces of where values were updated, providing clarity on interactions such as gestures affecting app behavior.
  • In practical examples like the Landmarks app, user interactions (e.g., tapping buttons) can lead to unexpected multiple updates within views.

Understanding View Updates in SwiftUI

Analyzing the Issue with Favorite Button Taps

  • It appears that tapping a single favorite button triggers updates across multiple item views instead of just the selected one. This necessitates a review of the underlying code to identify the cause.

Exploring ModelData and Observable Properties

  • The modelData.isFavorite function checks if a landmark is marked as a favorite by accessing the favoritesCollection.landmarks array, establishing dependencies between item views and the entire favorites array. This leads to all item views updating when any change occurs in favorites.

Dependency Issues with Item Views

  • Each LandmarkListItemView calls the isFavorite function to determine its icon's highlight status, resulting in unnecessary re-renders for all views whenever an update occurs in the favorites collection. Only one view should ideally be updated based on user interaction.

Need for Granular Data Dependencies

  • To improve efficiency, there’s a need for more granular data dependencies so that only affected view bodies are updated upon changes in app data. A new approach involves creating an observable view model for each view to track individual favorite statuses directly.

Implementing Individual View Models

  • By storing individual view models within the ModelData class, each view can toggle its own favorite status without relying on the entire favorites array, thus minimizing unnecessary updates across other views when changes occur. This results in only relevant views being refreshed upon state changes.

Evaluating Improvements Through Cause & Effect Graph

Observing Update Efficiency Post Changes

  • After implementing new view model improvements, only two updates were recorded after changing two favorites, indicating improved efficiency compared to previous implementations where multiple updates occurred unnecessarily. The Cause & Effect graph reflects this reduction effectively.

Understanding Environment Dependencies

  • When discussing environment values stored in EnvironmentValues, it’s noted that every view accessing these values creates dependencies on them; thus, any update prompts potential re-renders of those views depending on whether their accessed value has changed or not.

Exploring External Environment Updates

Types of Environment Updates Illustrated

  • Two main types of nodes represent environment updates: External Environment (app-level changes) and EnvironmentWriter (changes made within SwiftUI). For instance, switching color schemes due to dark mode triggers external environment updates reflected in the Cause & Effect graph for dependent views like View1 and View2 accordingly.

Understanding View Updates in SwiftUI

The Cost of View Updates

  • Both view updates are highlighted simultaneously to simplify identification. Even if a view's body doesn't need to run due to an environment update, there is still a cost associated with checking for updates.
  • The time spent on these checks can accumulate quickly, especially in applications with numerous views reading from the environment.
  • It is crucial to avoid storing frequently updating values, such as geometry values or timers, in the environment.

Visualizing Data Flow

  • The Cause & Effect Graph serves as an effective tool for visualizing data flow within your app, ensuring that views only update when necessary.
  • Unnecessary view body updates can significantly impact performance; thus, designing data flow carefully is essential.

Best Practices for Performance

  • Focus on keeping view bodies fast to allow SwiftUI sufficient time to render the UI without delays.
  • Utilize Instruments early and often during development to analyze app performance effectively.

Key Takeaways

  • Ensure that view bodies update quickly and only when needed for optimal SwiftUI performance.
  • Use the SwiftUI instrument throughout development to verify app performance and explore additional features through linked documentation.
Video description

Discover the new SwiftUI instrument. We’ll cover how SwiftUI updates views, how changes in your app’s data affect those updates, and how the new instrument helps you visualize those causes and effects. To get the most out of this session, we recommend being familiar with writing apps in SwiftUI. Explore related documentation, sample code, and more: Performance and metrics: https://developer.apple.com/documentation/Xcode/performance-and-metrics Measuring your app’s power use with Power Profiler: https://developer.apple.com/documentation/Xcode/measuring-your-app-s-power-use-with-power-profiler Understanding and improving SwiftUI performance: https://developer.apple.com/documentation/Xcode/understanding-and-improving-swiftui-performance Analyzing the performance of your visionOS app: https://developer.apple.com/documentation/visionOS/analyzing-the-performance-of-your-visionOS-app Improving app responsiveness: https://developer.apple.com/documentation/Xcode/improving-app-responsiveness Optimize CPU performance with Instruments: https://developer.apple.com/videos/play/wwdc2025/308 Analyze hangs with Instruments: https://developer.apple.com/videos/play/wwdc2023/10248 Explore UI animation hitches and the render loop: https://developer.apple.com/videos/play/tech-talks/10855 Demystify SwiftUI performance: https://developer.apple.com/videos/play/wwdc2023/10160 Explore SwiftUI animation: https://developer.apple.com/videos/play/wwdc2023/10156 Compose custom layouts with SwiftUI: https://developer.apple.com/videos/play/wwdc2022/10056 Demystify SwiftUI: https://developer.apple.com/videos/play/wwdc2021/10022 00:00 - Introduction & Agenda 02:19 - Discover the SwiftUI instrument 04:20 - Diagnose and fix long view body updates 19:54 - Understand causes and effects of SwiftUI updates 35:01 - Next steps More Apple Developer resources: Video sessions: https://apple.co/VideoSessions Documentation: https://apple.co/DeveloperDocs Forums: https://apple.co/DeveloperForums App: https://apple.co/DeveloperApp