Highlight
Xcode 13’s static analyzer can scan Objective-C, C, and C++ code without running the app, finding null return values, assertion side effects, infinite loops, redundant conditions, and C++ move/forward usage errors early.
Core Content
For an app that mixes Swift and Objective-C, not all common problems can be prevented by the compiler.
The example in the talk is a solar system browsing app. There is an Objective-C method in the projectpositionAtDate, the method declaration returns a non-null (non-empty) value. Swift code will call it.
The problem is that a certainswitchThe default branch is not initializedposition. The method finally returnsnil. The compilation may pass, but the test may not cover this path. Get this when Swift is runningnil, the behavior is unpredictable.
In the past, to find this kind of bug, developers usually had to wait for the crash report, add logs, reproduce the user path, and then go back to the code to guess which branch was missed. The rarer the path, the slower the troubleshooting.
Xcode’s Static Analyzer brings this step forward to the development stage. It does not run the app, it only analyzes the source code. It will deduce variable states along control flow and data flow, finding logical errors in rare paths.
Xcode 13 continues to expand the scope of inspections. It can find side effects, infinite loops, redundant branch conditions in assertions, and C++moveandforwardusage error.
From “find the problem” to “explain the path”
The value of a static analyzer is not just to report a warning.
In the sample project, Xcode’s issue navigator expands a list of events. Last event shownpositionyesnil. Look up,regularPositionAtDatewas not called. Continuing to look up, the receiving object starts from the default branchnil。
What the developer gets is a readable path: which branch is entered and which object remainsnil, which call was skipped, and why it returned in the endnil。
The fix is also straightforward: letdefaultBranch and spherical (spherical) branch using the same logic, then rerun Analyze.
Put checks into daily development
Static analysis can be run manually or incorporated into the build.
The manual way is Product > Analyze and the shortcut is Command-Shift-B. Xcode will analyze the source files of all targets in the current active scheme.
If you want to check it every time you build, you can turn on Analyze During ‘Build’ in Build Settings. Xcode will analyze only modified files like an incremental build.
When the project is large, you can also change it to shallow mode (shallow analysis mode). It’s faster, but avoids the problem of spanning multiple functions. For security-sensitive code, security issues (security issues) related checks can be turned on separately.
Detailed Content
Run the static analyzer (01:21)
The entry provided by Apple is in the Xcode menu. With the project open, select Product > Analyze, or press Command-Shift-B.
Xcode
└── Product
└── Analyze Command-Shift-B
Key points:
ProductIs Xcode’s build-related menu. -AnalyzeThe static analyzer will be started. -Command-Shift-BThey are shortcut keys for the same action.- The scope of analysis is the source files of all targets in the current active scheme.
This step is similar to build, but with different goals. The build checks whether the code compiles, and the static analyzer checks the code path for potential bugs.
Read the event chain of the analysis report (02:41)
After the analyzer finds an issue, you can expand the report in the issue navigator. The report lists the sequence of events that led to the bug and marks these events with arrows in the editor.
The core paths in the talk can be organized into the following minimal Objective-C example. It corresponds to the fact in the verbatim draft:positionAtDateThe statement returns non-null, but the default branch letspositionKeepnil。
typedef NS_ENUM(NSInteger, BodyShape) {
BodyShapeSpherical,
BodyShapeIrregular,
BodyShapeUnknown,
};
- (nonnull Position *)positionAtDate:(NSDate *)date {
Position *position = nil;
switch (self.shape) {
case BodyShapeSpherical:
position = [self regularPositionAtDate:date];
break;
case BodyShapeIrregular:
position = [self irregularPositionAtDate:date];
break;
default:
break;
}
return position;
}
Key points:
BodyShapeUnknownIndicates that it has not been selected by the first twocaseCovered shape. -positionThe initial value isnil。BodyShapeSphericalThe branch will callregularPositionAtDate:initializationposition。BodyShapeIrregularThe branch will call another method to initializeposition。defaultbranch directlybreak,positionstillnil.- The method return type is marked as
nonnull,returnnilIt will break this agreement.
The fix is to have the default branch also initializedposition. The fix adopted in the talk was to make the default case the same as the spherical case.
- (nonnull Position *)positionAtDate:(NSDate *)date {
Position *position = nil;
switch (self.shape) {
case BodyShapeSpherical:
default:
position = [self regularPositionAtDate:date];
break;
case BodyShapeIrregular:
position = [self irregularPositionAtDate:date];
break;
}
return position;
}
Key points:
defaultandBodyShapeSphericalshare the same logic.- When entering the default branch,
regularPositionAtDate:will be called. -positionNo longer staying along the default branchnil. - After fixing, re-run Analyze using the same tool to confirm that the problem is gone.
Check for side effects in assertions (04:49)
Xcode 13 adds assertion side effects checks. The example in the verbatim draft is to traverse the celestial body array, count the number of objects with satellites, and then useNSAssertThe number of verifications does not exceed the number of planets.
The wrong way to write it is to put the count update into the assertion expression.
NSUInteger objectsWithMoons = 0;
for (AstronomicalObject *object in objects) {
if (object.moonCount > 0) {
NSAssert(++objectsWithMoons <= planetCount,
@"Objects with moons must not exceed planets");
}
}
Key points:
objectsWithMoonsNumber of objects with satellites recorded. -forLoop through the celestial body array. -object.moonCount > 0Indicates that the current object has satellites. -++objectsWithMoonsThe counter will be modified, which is a side effect.- This side effect occurs when
NSAssertin the expression. - Assertions may be disabled in release builds and counter updates may be lost.
The fix is to move the side effect outside the assertion.
NSUInteger objectsWithMoons = 0;
for (AstronomicalObject *object in objects) {
if (object.moonCount > 0) {
objectsWithMoons += 1;
NSAssert(objectsWithMoons <= planetCount,
@"Objects with moons must not exceed planets");
}
}
Key points:
objectsWithMoons += 1Independent execution, does not depend on whether assertions are enabled. -NSAssertOnly conditions are checked.- Counter updates are consistent between debug builds and release builds.
-Same kind of checking also applies to C and C++
assert。
Check for infinite loops (06:02)
Xcode 13 can also find infinite loops. The example in the verbatim manuscript is that when filling a two-dimensional grid, the inner loop is mistakenly addedvalue, without incrementing the real loop countercolumn。
int value = 0;
for (int row = 0; row < rowCount; row++) {
for (int column = 0; column < columnCount; value++) {
grid[row][column] = value;
}
}
Key points:
- For outer circulation
rowIterate over rows. - Inner loop conditional dependencies
column < columnCount. - Writing of inner loop body
grid[row][column]. - The update expression is
value++。 columnWithout change, the loop condition may always hold.- The analyzer will report this type of error and explain which variable was not updated as expected.
The fix is to incrementallycolumn。
int value = 0;
for (int row = 0; row < rowCount; row++) {
for (int column = 0; column < columnCount; column++) {
grid[row][column] = value;
value++;
}
}
Key points:
column++Let the inner loop advance toward the termination condition. -grid[row][column]Still using the current row and column for writing. -value++Moved to the loop body to generate the next written value.- Control variables and business variables are separated, and errors are easier to find by people and tools.
Run automatically in build (06:47)
Xcode allows you to plug profilers into the build process. Go to Build Settings withanalysisSearch for relevant settings and open Analyze During ‘Build’.
Build Settings
└── Search: analysis
└── Analyze During 'Build' = Yes
Key points:
Build SettingsIt is the project build setting entry.- search
analysisYou can only view static analysis related options. -Analyze During 'Build'Once turned on, analysis will be triggered on every build. - The analysis process is similar to an incremental build, only modified files are processed.
- For projects that are sensitive to build time, shallow mode can be selected.
If you only want to examine a single file, you can select Single File Analysis from the Product > Perform Action menu.
Xcode
└── Product
└── Perform Action
└── Analyze <Current File>
Key points:
- Single file analysis is suitable for quick inspection when an implementation file has just been modified.
- Especially useful when modifying header files.
- It avoids reparsing all files that import this header file.
Core Takeaways
-
What: Perform non-null return value inspection for Objective-C and Swift hybrid boundaries. Why it’s worth doing: The bug in the talk happened in Objective-C
nonnullmethod returnsnil, runtime exceptions may occur after Swift calls. How to start: Start with the Objective-C API that Swift will call, run Product > Analyze, and prioritize reports with empty return values and empty receiving objects. -
What to do: Clean up the assertion to “only check, do not modify state”. Why it’s worth doing: Xcode 13 can discover
NSAssert、Cassert、C++assertThese side effects may disappear in the release build. How to get started: SearchNSAssertandassert,Bundle++, assignment, memory writing and other operations are moved to the line before the assertion. -
What: Add a static analysis check for complex loops. Why it’s worth doing: Two-dimensional grids, paged traversals, and state machine loops often mix control variables and business variables. Xcode 13 can find infinite loops caused by loop variables not advancing. How to start: After changing the loop logic, run the single file analysis of Product > Perform Action on the current file and process the infinite loop report first.
-
What: Automatically run Analyze when the core module is built. Why it’s worth doing: Static analysis does not require tests to cover rare paths, and is suitable for early detection of security issues, logic problems and API misuse. How to get started: Search in Build Settings
analysis, turn on Analyze During ‘Build’, and then press the module to decide whether to use deep mode or shallow mode. -
What to do: Turn on special security checks for security-sensitive code. Why it’s worth doing: The talk clearly mentioned that the analyzer can check for security issues and is suitable for handling code such as login, payment, local files and network input. How to start: Selectively enable security-related analyzer checks in Build Settings, and turn off noisy checks individually.
Related Sessions
- Detect and diagnose memory issues — Use Xcode, XCTest, and MetricKit to locate memory growth and regression.
- Triage TestFlight crashes in Xcode Organizer — Quickly cluster and locate crashes that have occurred on the TestFlight user side.
- Symbolication: Beyond the basics — Understand how debugging tools restore runtime addresses to function and source code locations.
Comments
GitHub Issues · utterances