-
Analyze hangs with Instruments
User interface elements often mimic real-world interactions, including real-time responses. Apps with a noticeable delay in user interaction — a hang — can break that illusion and create frustration. We'll show you how to use Instruments to analyze, understand, and fix hangs in your apps on all Apple platforms. Discover how you can efficiently navigate an Instruments trace document, interpret trace data, and record additional profiling data to better understand your specific hang.
If you aren't familiar with using Instruments, we recommend first watching "Getting Started with Instruments." And to learn about other tools that can help you discover hangs in your app, check out "Track down hangs with Xcode and on-device detection."Chapitres
- 1:56 - What is a hang?
- 3:51 - What is instant?
- 4:39 - Event handling and rendering loop
- 8:25 - Keep main thread work below 100ms
- 9:15 - Busy main thread hang
- 14:26 - Too long or too often?
- 21:46 - LazyVGrid still hangs on iPad
- 24:31 - Fix: Use task modifier to load thumbnail asynchronously
- 25:52 - Asynchronous hangs
- 32:38 - Fix: Get off of the main actor
- 35:57 - Blocked Main Thread Hang
- 39:19 - Fix: Make shared property async
- 40:35 - Blocked Thread does not imply unresponsive app
Ressources
Vidéos connexes
WWDC23
WWDC22
WWDC21
Tech Talks
WWDC20
WWDC19
WWDC16
-
Rechercher dans cette vidéo…
-
-
19:38 - BackgroundThumbnailView
struct BackgroundThumbnailView: View { static let thumbnailSize = CGSize(width:128, height:128) var background: BackyardBackground var body: some View { Image(uiImage: background.thumbnail) } } -
19:58 - BackgroundSelectionView with Grid
var body: some View { ScrollView { Grid { ForEach(backgroundsGrid) { row in GridRow { ForEach(row.items) { background in BackgroundThumbnailView(background: background) .onTapGesture { selectedBackground = background } } } } } } } -
20:03 - BackgroundSelectionView with Grid (simplified)
var body: some View { ScrollView { Grid { ForEach(backgroundsGrid) { row in GridRow { ForEach(row.items) { background in BackgroundThumbnailView(background: background) } } } } } } -
20:26 - LazyVGrid variant
var body: some View { ScrollView { LazyVGrid(columns: [.init(.adaptive(minimum: BackgroundThumbnailView.thumbnailSize.width))]) { ForEach(BackyardBackground.allBackgrounds) { background in BackgroundThumbnailView(background: background) } } } } -
24:05 - BackgroundThumbnailView
struct BackgroundThumbnailView: View { static let thumbnailSize = CGSize(width:128, height:128) var background: BackyardBackground var body: some View { Image(uiImage: background.thumbnail) } } -
24:59 - BackgroundThumbnailView with progress (but without loading)
struct BackgroundThumbnailView: View { static let thumbnailSize = CGSize(width:128, height:128) var background: BackyardBackground @State private var image: UIImage? var body: some View { if let image { Image(uiImage: image) } else { ProgressView() .frame(width: Self.thumbnailSize.width, height: Self.thumbnailSize.height, alignment: .center) } } } -
25:26 - BackgroundThumbnailView with async loading on main thread
struct BackgroundThumbnailView: View { static let thumbnailSize = CGSize(width:128, height:128) var background: BackyardBackground @State private var image: UIImage? var body: some View { if let image { Image(uiImage: image) } else { ProgressView() .frame(width: Self.thumbnailSize.width, height: Self.thumbnailSize.height, alignment: .center) .task { image = background.thumbnail } } } } -
29:59 - BackgroundThumbnailView with async loading on main thread
struct BackgroundThumbnailView: View { static let thumbnailSize = CGSize(width:128, height:128) var background: BackyardBackground @State private var image: UIImage? var body: some View { if let image { Image(uiImage: image) } else { ProgressView() .frame(width: Self.thumbnailSize.width, height: Self.thumbnailSize.height, alignment: .center) .task { image = background.thumbnail } } } } -
31:41 - BackgroundThumbnailView with async loading on main thread (simplified)
struct BackgroundThumbnailView: View { // [...] var body: some View { // [...] ProgressView() .task { image = background.thumbnail } // [...] } } -
33:40 - BackgroundThumbnailView with async loading on main thread
struct BackgroundThumbnailView: View { static let thumbnailSize = CGSize(width:128, height:128) var background: BackyardBackground @State private var image: UIImage? var body: some View { if let image { Image(uiImage: image) } else { ProgressView() .frame(width: Self.thumbnailSize.width, height: Self.thumbnailSize.height, alignment: .center) .task { image = background.thumbnail } } } } -
33:59 - synchronous thumbnail property
public var thumbnail: UIImage { get { // compute and cache thumbnail } } -
34:03 - asynchronous thumbnail property
public var thumbnail: UIImage { get async { // compute and cache thumbnail } } -
34:08 - BackgroundThumbnailView with async loading in background
struct BackgroundThumbnailView: View { static let thumbnailSize = CGSize(width:128, height:128) var background: BackyardBackground @State private var image: UIImage? var body: some View { if let image { Image(uiImage: image) } else { ProgressView() .frame(width: Self.thumbnailSize.width, height: Self.thumbnailSize.height, alignment: .center) .task { image = await background.thumbnail } } } } -
38:52 - shared property causes blocked main thread
var body: some View { mainContent .task(id: imageMode) { defer { loading = false } do { var image = await background.thumbnail if imageMode == .colorized { let colorizer = ColorizingService.shared image = try await colorizer.colorize(image) } self.image = image } catch { self.error = error } } } -
39:00 - shared property causes blocked main thread (simplified)
struct ImageTile: View { // [...] // implicit @MainActor var body: some View { mainContent .task() { // inherits @MainActor isolation // [...] let colorizer = ColorizingService.shared result = try await colorizer.colorize(image) } } } -
39:10 - shared property causes blocked main thread + ColorizingService (simplified)
class ColorizingService { static let shared = ColorizingService() // [...] } struct ImageTile: View { // [...] // implicit @MainActor var body: some View { mainContent .task() { // inherits @MainActor isolation // [...] let colorizer = ColorizingService.shared result = try await colorizer.colorize(image) } } } -
39:25 - shared synchronous property after await keyword still causes blocked main thread
class ColorizingService { static let shared = ColorizingService() // [...] } struct ImageTile: View { // [...] // implicit @MainActor var body: some View { mainContent .task() { // inherits @MainActor isolation // [...] result = try await ColorizingService.shared.colorize(image) } } } -
class ColorizingService { static let shared = ColorizingService() func colorize(_ grayscaleImage: CGImage) async throws -> CGImage // [...] } struct ImageTile: View { // [...] // implicit @MainActor var body: some View { mainContent .task() { // inherits @MainActor isolation // [...] result = try await ColorizingService.shared.colorize(image) } } }
-