Setting Breakpoints and Inspecting Variables and Call Stacks
Course: iOS App Development
Topic: Setting Breakpoints and Inspecting Variables and Call Stacks
Course Link: iOS App Development Training
Demo Link: Register for a Free Course Demo
Website: JustAcademy
1. Introduction to Debugging in iOS Development
Debugging is the process of identifying, analyzing, and fixing errors or unexpected behavior in an application. During iOS development, developers use Xcode's debugging tools to understand how code executes, inspect variable values, trace function calls, and locate the source of problems.
One of the most important debugging techniques is setting breakpoints. A breakpoint temporarily pauses program execution at a selected line of code, allowing developers to examine the application's current state.
Xcode also provides variable inspection and call stack information. These features help developers understand what values are stored in memory and how the application reached a particular point during execution.
Why Debugging Is Important
- Identifies logical errors and unexpected application behavior.
- Helps locate the exact line where a problem occurs.
- Allows developers to inspect variable values during execution.
- Helps understand function execution order.
- Improves application stability and reliability.
- Reduces development and troubleshooting time.
- Helps investigate crashes and runtime exceptions.
2. Understanding Breakpoints in Xcode
A breakpoint is a debugging marker placed on a specific line of code. When the application reaches that line during a debugging session, execution pauses before the selected statement runs. Developers can then inspect variables, evaluate expressions, and examine the call stack.
Purpose of Breakpoints
- Pause execution at an important section of code.
- Inspect values before and after an operation.
- Identify where incorrect data enters a function.
- Understand conditional logic and loops.
- Investigate crashes and unexpected results.
- Trace how data moves between functions.
How to Set a Breakpoint
- Open your iOS project in Xcode.
- Open the Swift file containing the code you want to debug.
- Locate the line where execution should pause.
- Click the gutter area to the left of that line number.
- A blue breakpoint marker appears.
- Run the application using the Run button.
- Perform the action that executes the selected code.
- Xcode pauses execution when the breakpoint is reached.
- Inspect variables and the call stack in the debugging area.
Example: Basic Breakpoint
func calculateTotal(price: Double, quantity: Int) -> Double {
let total = price * Double(quantity)
let tax = total * 0.18
let finalAmount = total + tax
return finalAmount
}
let result = calculateTotal(price: 500, quantity: 2)
print(result)
Place a breakpoint on the line that calculates total. When the function executes, Xcode pauses and allows you to inspect price, quantity, and other variables.
3. Types of Breakpoints in Xcode
Xcode supports different breakpoint types for debugging different situations. Selecting the appropriate breakpoint helps developers investigate problems more efficiently.
| Breakpoint Type |
Description |
Common Use |
| Line Breakpoint |
Pauses execution at a selected source-code line. |
Inspecting variables and program flow. |
| Conditional Breakpoint |
Pauses execution only when a specified condition is true. |
Debugging specific loop iterations or values. |
| Exception Breakpoint |
Pauses execution when a configured exception occurs. |
Investigating runtime exceptions. |
| Symbolic Breakpoint |
Pauses when execution reaches a specified symbol or function. |
Debugging a method without locating every call site. |
| Runtime Issue Breakpoint |
Helps stop execution when certain runtime issues are detected, depending on configuration. |
Investigating runtime problems. |
| Test Failure Breakpoint |
Helps pause execution when a test failure occurs in supported debugging workflows. |
Debugging failing tests. |
4. Working with Line Breakpoints
Line breakpoints are the most commonly used breakpoints in Xcode. They allow developers to pause execution at a specific statement and inspect the program state.
Steps to Use Line Breakpoints
- Open the required Swift source file.
- Find the statement you want to investigate.
- Click the gutter beside the line number.
- Run the application in Debug mode.
- Execute the feature containing that statement.
- Wait for Xcode to pause execution.
- Inspect the variables and call stack.
- Continue execution or step through the code.
Example: Debugging Login Validation
func validateLogin(username: String, password: String) -> Bool {
let isUsernameValid = !username.isEmpty
let isPasswordValid = password.count >= 8
let isValid = isUsernameValid && isPasswordValid
return isValid
}
let loginStatus = validateLogin(
username: "developer",
password: "password123"
)
print(loginStatus)
Set breakpoints on the validation statements. You can inspect the username, password length, Boolean validation results, and final login status.
5. Setting Conditional Breakpoints
A conditional breakpoint pauses execution only when a specified expression evaluates to true. This is especially useful when a statement runs repeatedly inside a loop or when a function receives many different inputs.
Why Use Conditional Breakpoints?
- Avoids stopping at every loop iteration.
- Helps investigate a specific input value.
- Reduces unnecessary debugging interruptions.
- Makes it easier to identify unusual conditions.
- Helps debug problems that occur only for particular users or data.
Example: Finding a Specific Loop Iteration
func printNumbers() {
for number in 1...10 {
print(number)
}
}
printNumbers()
To pause only when number == 5:
- Set a breakpoint on the
print(number) statement.
- Right-click or Control-click the breakpoint marker.
- Select Edit Breakpoint.
- Enter the condition
number == 5.
- Run the application.
- Execution pauses when the condition becomes true.
Example: Conditional Breakpoint for a Price
func applyDiscount(price: Double) -> Double {
let discountedPrice = price * 0.90
return discountedPrice
}
let first = applyDiscount(price: 100)
let second = applyDiscount(price: 500)
let third = applyDiscount(price: 1000)
A conditional breakpoint on the function's first executable line with the condition price >= 500 helps inspect only calls involving prices of 500 or more.
6. Setting Exception Breakpoints
An exception breakpoint helps developers pause execution when an exception occurs. It is useful when an application crashes or encounters an unexpected runtime failure.
Common Situations for Exception Breakpoints
- Investigating array index errors.
- Debugging invalid operations.
- Identifying exceptions raised by system or application code.
- Finding the location where a runtime failure originates.
How to Add an Exception Breakpoint
- Open the Breakpoint Navigator in Xcode.
- Click the plus (+) button.
- Select Add Exception Breakpoint.
- Choose the exception type and available options.
- Run the application.
- Reproduce the problem.
- Xcode pauses when the configured exception is detected.
- Inspect the source location and call stack.
Example: Array Index Error
let names = ["Amit", "Riya", "John"]
print(names[5])
This code attempts to access an index that does not exist. Swift array indexing with an invalid index causes a runtime trap rather than a normal recoverable exception. A suitable debugging breakpoint or crash backtrace can help identify the failing line.
7. Understanding Symbolic Breakpoints
A symbolic breakpoint pauses execution when a particular function, method, or symbol is reached. Unlike a line breakpoint, it does not require developers to place a marker on a specific source-code line.
When to Use Symbolic Breakpoints
- Debugging a function called from multiple locations.
- Investigating lifecycle methods.
- Tracking calls to a particular framework method.
- Debugging code when its source location is difficult to identify.
How to Add a Symbolic Breakpoint
- Open the Breakpoint Navigator.
- Click the plus (+) button.
- Select Add Symbolic Breakpoint.
- Enter the function or symbol name.
- Configure any required module or condition information.
- Run the application.
- Execution pauses when the matching symbol is reached, if it is available and resolved.
8. Inspecting Variables in Xcode
Variable inspection is the process of checking the values stored in variables while an application is paused during debugging. It helps developers understand how data changes throughout program execution.
When a breakpoint is reached, Xcode displays available variables in the debugging area. Developers can expand objects, inspect properties, and evaluate expressions.
Types of Variables You Can Inspect
- Local Variables: Variables declared inside a function or block.
- Function Parameters: Values passed into a function.
- Instance Properties: Values belonging to an object.
- Class Properties: Properties associated with a class or type.
- Constants: Values declared using
let.
- Optional Values: Values that may contain a value or be nil.
- Collections: Arrays, dictionaries, sets, and other collection types.
Example: Inspecting Local Variables
func calculateBill(amount: Double, taxRate: Double) -> Double {
let tax = amount * taxRate
let total = amount + tax
return total
}
let bill = calculateBill(amount: 1000, taxRate: 0.18)
print(bill)
Set a breakpoint on the return statement. You can inspect amount, taxRate, tax, and total to understand the calculation.
9. Inspecting Variables Using the Debug Area
The Xcode Debug Area appears while debugging and provides information about the current execution state.
Steps to Inspect Variables
- Run the application using Xcode's debugging configuration.
- Wait until a breakpoint is reached.
- Open the Debug Area if it is hidden.
- Locate the variables view.
- Expand the variable you want to inspect.
- Review its current value and available properties.
- Use the variable information to identify incorrect data or unexpected state.
Example: Inspecting an Array
func displayUsers() {
let users = ["Amit", "Riya", "John", "Sara"]
let userCount = users.count
print("Total users: \(userCount)")
}
displayUsers()
Place a breakpoint after the userCount declaration. Expand users in the variables view to inspect its elements and check the value of userCount.
10. Inspecting Optional Values
Optionals are an important feature of Swift. An optional variable can contain a value or nil. Inspecting optional values during debugging helps developers identify missing data and avoid unexpected nil-related problems.
Example: Optional Variable
func findUsername() -> String? {
return "developer"
}
let username = findUsername()
if let name = username {
print("Username: \(name)")
} else {
print("Username not found")
}
What to Check During Debugging
- Whether the optional contains a value.
- Whether the optional is nil.
- Where the value was assigned.
- Whether optional binding works as expected.
- Whether an unexpected nil value causes incorrect behavior.
11. Using the Variables View and Quick Look
The variables view helps developers explore values while the application is paused. Xcode may also provide Quick Look support for certain data types, allowing developers to preview objects such as images and other supported values.
Common Inspection Techniques
- Expand a variable to inspect its properties.
- Review array and dictionary contents.
- Inspect object references and nested values.
- Preview supported visual objects using Quick Look.
- Use the debugger console to evaluate expressions.
Example: Inspecting an Image
let image = UIImage(named: "profile")
if let profileImage = image {
print("Image loaded successfully")
} else {
print("Image not found")
}
Set a breakpoint inside the successful branch. If supported by the current Xcode version and debugging context, Quick Look can help preview the image value.
12. Understanding the Call Stack
A call stack is a record of the active function calls at a particular moment during program execution. It shows how the application reached the current line of code.
Whenever a function calls another function, a new stack frame is created. When a function completes, its stack frame is removed. The call stack helps developers trace the sequence of function calls and investigate the source of errors.
Why the Call Stack Is Important
- Shows which function is currently executing.
- Displays the chain of function calls.
- Helps identify where a problem originated.
- Provides context when a crash occurs.
- Helps investigate unexpected function execution.
- Supports debugging of nested function calls.
Example: Function Call Sequence
func showMessage() {
print("Welcome")
}
func loadProfile() {
showMessage()
}
func startApplication() {
loadProfile()
}
startApplication()
When execution pauses inside showMessage(), the call stack can show the active sequence:
showMessage() – current function.
loadProfile() – function that called showMessage.
startApplication() – function that called loadProfile.
- Global or entry-point execution – depending on the application context.
13. Inspecting the Call Stack in Xcode
Xcode displays call stack information in the debugging area when execution is paused. Developers can select different stack frames to inspect the local variables and execution context associated with each function.
Steps to Inspect the Call Stack
- Set a breakpoint inside a function.
- Run the application.
- Execute the code that reaches the breakpoint.
- Open the Debug Area.
- Locate the call stack section.
- Review the listed stack frames.
- Select a stack frame to inspect its context.
- Examine the available variables and source location.
- Move between frames to understand how execution reached the current function.
Understanding Stack Frames
A stack frame represents one active function call. It contains execution-related information such as the function context, local variables, and return information.
| Term |
Meaning |
| Stack Frame |
Information associated with an active function call. |
| Current Frame |
The function currently selected or being inspected. |
| Caller |
The function that invoked another function. |
| Callee |
The function that is invoked by another function. |
| Backtrace |
A representation of the active call sequence. |
| Return Address |
Information identifying where execution should continue after a function returns. |
14. Navigating Between Stack Frames
When a breakpoint is reached, developers can select different stack frames to understand the context of each function call. This is useful when a problem occurs deep inside a chain of function calls.
Example: Nested Function Calls
func calculateTax(_ amount: Double) -> Double {
return amount * 0.18
}
func calculateTotal(_ amount: Double) -> Double {
let tax = calculateTax(amount)
return amount + tax
}
func processPayment() {
let total = calculateTotal(1000)
print("Payment: \(total)")
}
processPayment()
Place a breakpoint inside calculateTax(). The call stack helps trace the execution through calculateTotal() and processPayment(). Selecting different frames allows you to inspect the variables available in each function's context.
15. Using the Debugger Console and LLDB
LLDB is the debugger used by Xcode for inspecting and controlling program execution. The Xcode debug console allows developers to evaluate expressions, inspect variables, and execute debugger commands while the application is paused.
Common LLDB Commands
| Command |
Purpose |
po variableName |
Prints an object using its description when supported. |
p variableName |
Evaluates and prints an expression. |
v variableName |
Displays a variable using LLDB's variable inspection facilities. |
bt |
Displays the current thread's backtrace. |
thread backtrace |
Displays the current thread's call stack. |
continue |
Resumes program execution. |
frame variable |
Displays variables available in the selected stack frame. |
thread list |
Lists debugger threads. |
Example: Printing a Variable
func calculateDiscount(price: Double) -> Double {
let discount = price * 0.10
let finalPrice = price - discount
return finalPrice
}
let result = calculateDiscount(price: 2000)
Set a breakpoint inside the function. In the LLDB console, try evaluating:
po price
p discount
p finalPrice
The exact output depends on the selected stack frame, variable availability, compiler optimization, and debugging context.
16. Stepping Through Code During Debugging
Xcode provides execution controls that allow developers to move through code one step at a time. These controls help understand the order of execution and inspect intermediate values.
| Debugging Control |
Purpose |
| Step Over |
Executes the current line and moves to the next line without entering a called function. |
| Step Into |
Enters the function called by the current statement when possible. |
| Step Out |
Continues execution until the current function returns. |
| Continue |
Resumes execution until another breakpoint or stopping event is reached. |
| Pause |
Interrupts running execution for inspection when supported by the current state. |
| Stop |
Terminates the current debugging session. |
Example: Step Into and Step Over
func multiply(_ a: Int, _ b: Int) -> Int {
return a * b
}
func calculate() {
let result = multiply(5, 4)
print(result)
}
calculate()
When execution pauses on the call to multiply(), use Step Into to enter the function. Use Step Over to execute the function call without entering it. Use Step Out to finish the current function and return to its caller.
17. Debugging View Controllers and App Lifecycle Methods
Breakpoints are useful for investigating view controller lifecycle methods and understanding when screens are loaded, displayed, and removed.
Common UIKit Lifecycle Methods
viewDidLoad() – called after the view controller's view is loaded into memory.
viewWillAppear(_:) – called before the view is added to the view hierarchy.
viewDidAppear(_:) – called after the view has appeared.
viewWillDisappear(_:) – called before the view disappears.
viewDidDisappear(_:) – called after the view disappears.
Example: Debugging a View Controller
import UIKit
class ProfileViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
print("Profile screen loaded")
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
print("Profile screen will appear")
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
print("Profile screen appeared")
}
}
Place breakpoints inside each lifecycle method. Navigate between screens and observe the order in which the methods execute.
18. Debugging Data Flow Between Functions
Many application bugs occur because incorrect data is passed between functions. Breakpoints and variable inspection help developers trace how values change from one function to another.
Example: Tracking User Data
struct User {
let name: String
let age: Int
}
func createUser() -> User {
return User(name: "Amit", age: 25)
}
func displayUser(_ user: User) {
print("Name: \(user.name)")
print("Age: \(user.age)")
}
let user = createUser()
displayUser(user)
Set a breakpoint in createUser() and another in displayUser(_:). Inspect the returned object and the function parameter to verify that the expected user information is passed correctly.
19. Debugging Asynchronous Code
Modern iOS applications frequently use asynchronous operations for API requests, image loading, database operations, and background processing. Debugging asynchronous code requires understanding that execution may continue later on a different thread or actor.
Example: Debugging an API Request
func fetchUsers() {
guard let url = URL(string: "https://example.com/api/users") else {
return
}
URLSession.shared.dataTask(with: url) { data, response, error in
if let error = error {
print("Error: \(error.localizedDescription)")
return
}
guard let data = data else {
print("No data received")
return
}
print("Received bytes: \(data.count)")
}.resume()
}
Debugging Tips for Asynchronous Operations
- Set breakpoints inside completion handlers.
- Inspect errors and response values.
- Check which thread or queue is executing the code.
- Verify that UI updates occur on the main thread or main actor.
- Use thread information to investigate unexpected execution behavior.
- Remember that a completion handler may execute after the calling function has returned.
20. Managing Breakpoints Effectively
Large applications may contain many breakpoints. Managing them properly helps keep debugging sessions organized.
Breakpoint Management Options
- Enable: Activates a breakpoint so execution can pause there.
- Disable: Temporarily prevents a breakpoint from stopping execution.
- Edit: Changes breakpoint conditions and actions.
- Delete: Removes a breakpoint that is no longer required.
- Move: Places a breakpoint on a different source-code line.
- Breakpoint Navigator: Displays and organizes project breakpoints.
Best Practices
- Use descriptive breakpoint names when available.
- Disable breakpoints that are not needed for the current task.
- Use conditional breakpoints for repeated code execution.
- Remove temporary breakpoints after debugging.
- Keep important debugging configurations organized.
- Avoid leaving unnecessary breakpoints in shared development workflows.
21. Common Debugging Problems and Solutions
| Problem |
Possible Cause |
Solution |
| Breakpoint does not activate |
Code path is not executed or breakpoint is disabled. |
Verify the execution path and breakpoint status. |
| Variable value is unavailable |
Variable is out of scope or optimized away. |
Select the correct stack frame and use a suitable debug build. |
| Unexpected variable value |
Incorrect assignment or unexpected data flow. |
Inspect earlier statements and function parameters. |
| Application pauses too frequently |
Too many active breakpoints. |
Disable unnecessary breakpoints or add conditions. |
| Call stack is difficult to understand |
Deep function calls or system framework frames. |
Inspect relevant frames and focus on application code. |
| Debugger cannot resolve a symbol |
Symbol information is unavailable or the symbol name is incorrect. |
Check the symbol name, build configuration, and debugging symbols. |
| Asynchronous breakpoint is confusing |
Code executes later or on another thread. |
Inspect the active thread and the completion handler. |
22. Practical Project: Debugging a User Profile Screen
Project Objective: Create a simple iOS user profile workflow and use Xcode breakpoints, variable inspection, and call stack analysis to understand its execution.
Project Requirements
- Xcode installed on macOS.
- Swift and UIKit or SwiftUI knowledge.
- A basic iOS application project.
- Simulator or compatible physical iOS device.
Sample Implementation
struct Profile {
let name: String
let email: String
}
func createProfile() -> Profile {
let profile = Profile(
name: "Amit Sharma",
email: "[email protected]"
)
return profile
}
func validateProfile(_ profile: Profile) -> Bool {
let isNameValid = !profile.name.isEmpty
let isEmailValid = profile.email.contains("@")
return isNameValid && isEmailValid
}
func displayProfile() {
let profile = createProfile()
let isValid = validateProfile(profile)
print("Profile: \(profile.name)")
print("Valid: \(isValid)")
}
displayProfile()
Project Implementation Steps
- Create a new iOS application in Xcode.
- Add the profile model and functions to the project.
- Set a line breakpoint inside
createProfile().
- Set another breakpoint inside
validateProfile(_:).
- Run the application.
- Inspect the profile values when the first breakpoint is reached.
- Use Step Into and Step Over to follow execution.
- Inspect the call stack to identify the function calling each method.
- Check the validation results and function parameters.
- Use the debugger console to evaluate relevant expressions.
- Modify the sample data to test invalid profile values.
- Repeat the debugging process and compare the results.
Expected Learning Outcomes
- Set and manage line breakpoints.
- Use conditional breakpoints to inspect specific values.
- Inspect local variables and function parameters.
- Navigate between stack frames.
- Understand how functions call one another.
- Use LLDB commands during debugging.
- Identify and fix logical errors.
23. Exercises for Practice
- Create a function that calculates the average of three numbers and inspect each variable using breakpoints.
- Set a conditional breakpoint inside a loop that executes ten times.
- Create a nested function-call example and inspect its call stack.
- Use Step Into, Step Over, and Step Out to understand execution flow.
- Set an exception breakpoint and investigate an array-index runtime failure.
- Inspect an optional variable when it contains a value and when it is nil.
- Use LLDB commands to print a variable and view the current backtrace.
- Set breakpoints inside a view controller's lifecycle methods.
- Debug an asynchronous network request using a breakpoint in its completion handler.
- Fix a logical error and verify the corrected variable values.
24. Interview Questions and Answers
Q1. What is a breakpoint in Xcode?
Answer: A breakpoint is a debugging marker that pauses application execution at a selected point so developers can inspect variables and program behavior.
Q2. How do you set a breakpoint in Xcode?
Answer: Click the gutter area beside the required line number in the source editor. Run the application, and execution pauses when that line is reached.
Q3. What is a conditional breakpoint?
Answer: A conditional breakpoint pauses execution only when a specified expression evaluates to true.
Q4. What is an exception breakpoint?
Answer: An exception breakpoint pauses execution when a configured exception occurs, helping developers investigate runtime failures.
Q5. What is a symbolic breakpoint?
Answer: A symbolic breakpoint pauses execution when a specified function or symbol is reached.
Q6. What is variable inspection?
Answer: Variable inspection is the process of examining current variable values while the application is paused during debugging.
Q7. What is a call stack?
Answer: A call stack displays the active sequence of function calls that led to the current execution point.
Q8. What is a stack frame?
Answer: A stack frame represents an active function call and contains execution context such as local variables and function-related information.
Q9. What is the difference between Step Into and Step Over?
Answer: Step Into enters a called function when possible, while Step Over executes the current statement without entering the called function.
Q10. What does Step Out do?
Answer: Step Out continues execution until the current function returns to its caller.
Q11. What is LLDB?
Answer: LLDB is the debugger used by Xcode to inspect program state, evaluate expressions, control execution, and analyze call stacks.
Q12. Why might a variable value be unavailable during debugging?
Answer: The variable may be outside the selected scope, unavailable in the current stack frame, or optimized away by the compiler.
25. Quick Revision Checklist
- Understand the purpose of debugging in iOS development.
- Know how to create and remove line breakpoints.
- Understand conditional, exception, and symbolic breakpoints.
- Inspect local variables and function parameters.
- Understand optional values and object properties.
- Use the Xcode Debug Area effectively.
- Understand call stacks and stack frames.
- Navigate between different stack frames.
- Use LLDB commands to inspect program execution.
- Practice Step Into, Step Over, Step Out, and Continue.
- Debug view controller lifecycle methods.
- Investigate asynchronous code and thread execution.
26. Conclusion
Setting breakpoints and inspecting variables and call stacks are essential debugging skills for iOS developers. Xcode provides a complete debugging environment that allows developers to pause execution, examine values, trace function calls, and investigate runtime problems.
By learning line breakpoints, conditional breakpoints, exception breakpoints, variable inspection, LLDB commands, and call stack navigation, developers can identify errors more quickly and understand how their applications execute.
Regular debugging practice helps developers write more reliable Swift code, troubleshoot complex application behavior, and build stable iOS applications.
27. Additional Learning Resources