UI Testing & Debugging with Xcode Instruments

iOS Development — Architecture & Data

Chapter 7 · UI Testing & Debugging with Xcode Instruments

Chapter 6's own unit tests verify a ViewModel's own logic in isolation. This chapter covers two genuinely different real tools: UI testing, which drives the actual real app UI end to end, and Instruments, Apple's own real profiling suite for finding performance and memory problems a unit test can't catch.

UI Testing: A Genuinely Different Layer

Chapter 6's own tests never launch a real app — they test a ViewModel directly. UI testing (built on the real XCUITest framework) does the opposite: it launches the actual real app and drives it exactly as a user would, tapping buttons and reading real, rendered content.

func testAddingATask() { let app = XCUIApplication() app.launch() app.buttons["addTaskButton"].tap() app.textFields["newTaskTitleField"].typeText("Buy milk") app.buttons["saveButton"].tap() XCTAssertTrue(app.staticTexts["Buy milk"].exists) }

Making Elements Findable: .accessibilityIdentifier()

XCUIApplication finds real UI elements by their accessibility identifier — a stable, real string that doesn't change with localization or a later text edit, unlike matching on a button's own visible label text.

Button("Add") { isShowingNewTask = true } .accessibilityIdentifier("addTaskButton")
A Real, Genuine Bonus
Real accessibility identifiers exist for accessibility itself first — VoiceOver and other real Apple accessibility tools use them too. Adding them for UI testing genuinely improves the app's own real accessibility as a direct side effect, not a separate task.

Debugging with the Xcode Debugger

Beyond automated tests, Xcode's own real, built-in debugger — breakpoints, stepping through code, and the console's real po (print object) command for inspecting a variable's own current value mid-run — remains the genuinely fastest tool for understanding a single, specific bug as it happens.

Instruments: Real Performance & Memory Profiling

Instruments is Apple's real, dedicated profiling application, launched from Xcode via Product → Profile, running the real app while measuring what it actually does.

Time Profiler

Real, sampled CPU usage — which functions are actually consuming the most processing time.

Allocations

Every real memory allocation and its full lifecycle — genuinely the right tool for retain cycles.

Leaks

Real, definite memory loss — allocated memory nothing can reach anymore at all.

A Real, Genuinely Non-Obvious Distinction
The Leaks instrument does not reliably catch a strong reference cycle (Fundamentals Chapter 4's own real ARC topic) — a retain cycle's own memory is technically still reachable through the cycle itself, so it never registers as a genuine "leak" the way memory with zero real references left does. The Allocations instrument is the genuinely correct real tool for retain cycles — watching for memory that keeps growing and never shrinks back down after a screen is dismissed is the real, practical signal something is being retained that should have been released.
A Real, Concrete Symptom to Watch For
Navigate into TaskDetailView and back out, repeatedly, while watching Allocations' own real memory graph. If the real memory count keeps climbing and never returns to its starting baseline, that's a genuine, practical sign a retain cycle is keeping old TaskDetailView instances alive that should have been deallocated.

Hands-On Exercises

Exercise 1

Add real .accessibilityIdentifier() modifiers to TaskFlow's own "Add" toolbar button and the "Save" button inside NewTaskView, then write a real XCUITest function that launches the app, taps "Add," types a title, taps "Save," and asserts the new task's own title appears somewhere in the app.

📄 View solution
Exercise 2

Explain, in your own words, why a UI test finding a button by its real, visible label text ("Add") is genuinely more fragile than finding it by a real accessibility identifier ("addTaskButton"), giving one concrete, realistic scenario where the label-text approach would break.

📄 View solution
Exercise 3

Explain, in your own words, why the Leaks instrument genuinely can't reliably detect a strong reference cycle, and why Allocations' own "memory that never shrinks back down" pattern is a real, practical signal of one instead.

📄 View solution

Chapter 7 Quick Reference

  • XCUITest launches and drives the real, actual app UI — a genuinely different layer from Chapter 6's own isolated ViewModel unit tests
  • .accessibilityIdentifier() gives real UI elements a stable, findable identity that doubles as a genuine accessibility improvement
  • The Xcode debugger (breakpoints, po) remains the fastest real tool for understanding one specific bug live
  • Time Profiler measures real CPU usage; Allocations and Leaks measure real memory, but for genuinely different things
  • Leaks catches definite memory loss only — Allocations, watching for memory that never shrinks back down, is the real, correct tool for finding strong reference cycles