Scheme in Xcode is a configuration that defines how to build, test, profile, and archive an app for iOS, macOS, watchOS, or tvOS. Each Scheme contains a set of actions (Build, Run, Test, Profile, Analyze, Archive) with its own parameters, arguments, and environment variables. According to Apple Developer Documentation, 2025, Scheme is the primary tool for managing build configurations in Xcode, replacing manual parameter switching. Xcode automatically creates a scheme for each target when the project is first opened.
Key takeaways
Scheme in Xcode is an XML file (with the .xcscheme extension) that describes a sequence of actions and their parameters for building and analyzing an app. Each Scheme is tied to one or more targets and defines which configuration (Debug, Release, AdHoc) to use for each action. Scheme is the equivalent of the Build Variant in Android, but with a more flexible structure: one scheme can contain different targets for different actions.
Xcode automatically creates a scheme for each target when the project is first opened. The scheme name by default matches the target name. If the project has a test target, Xcode automatically adds it to the Test action of the main target's scheme. For projects with multiple targets (main app + watchOS + extension), Xcode creates a separate scheme for each one, but you can also create one scheme that builds all targets at once.
Schemes are stored in the xcshareddata/xcschemes/ directory (for shared) or xcuserdata/<user>/xcschemes/ (for private). Shared schemes go into Git and are used by the whole team. Private schemes are stored locally and are not synchronized. The .xcscheme file has an XML format with the root element <Scheme>. Inside are blocks for each action: BuildAction, TestAction, LaunchAction, ProfileAction, AnalyzeAction, ArchiveAction.
.xcscheme is an XML file that can be edited manually or through Xcode. The main elements: <BuildAction> (the list of targets to build), <TestAction> (links to test targets), <LaunchAction> (launch configuration), <ProfileAction>, <AnalyzeAction>, <ArchiveAction>. Each block contains the buildConfiguration attribute, which determines which configuration (Debug/Release) to use for the given action.
Scheme consists of six actions, each of which can be configured independently. The Build Action determines which targets are built and in what order. The Run Action determines how the app is launched: with which arguments, environment variables, and configuration. The Test Action determines which tests are run and which code coverage options are enabled. The Profile Action launches with Instruments tools for profiling. The Analyze Action performs static code analysis with the Clang Static Analyzer. The Archive Action builds for publication in the App Store or AdHoc distribution.
For each action you can set a separate build configuration. Usually Debug is used for Run and Test, and Release for Archive. The build configuration defines a set of compiler flags, optimizations, and debugging information. Xcode provides two standard configurations: Debug (no optimizations, with debug symbols) and Release (with optimizations, without debug information). Developers can add custom configurations via project.xcconfig.
The Archive Action is especially important — it creates an .xcarchive, which is then exported to an .ipa for the App Store or AdHoc. The Archive Action uses the Release configuration by default, but you can switch to AdHoc or Distribution. The Archive Action also has the revealArchiveInOrganizer flag — after archiving completes, Xcode opens the Organizer for further actions with the archive.
<!-- Example .xcscheme for an iOS application -->
<Scheme
LastUpgradeVersion = "1500"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "ABCD1234"
BuildableName = "MyApp.app"
BlueprintName = "MyApp"
ReferencedContainer = "container:MyApp.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
enableAddressSanitizer = "YES">
</LaunchAction>
</Scheme>
Creating a new scheme is done through the Xcode menu: Product → Scheme → New Scheme or with the "+" button in the Scheme panel (next to the Run button). When creating, the target for which the scheme is created is selected. Xcode automatically copies settings from an existing scheme if it is selected as a "duplicate". New schemes are saved as private by default — to share with the team, you need to enable Shared in Manage Schemes.
The Edit Scheme window (Product → Scheme → Edit Scheme) contains six tabs matching the number of actions. On each tab you can change the build configuration, launch arguments, environment variables, and diagnostic flags. On the Run tab the options are: executable (which binary to launch), wait for executable to be launched (for debugging launched processes), debugger (LLDB or None), launch arguments, environment variables, and extended options (Address Sanitizer, Thread Sanitizer, Main Thread Checker, Memory Management).
For diagnostics Address Sanitizer (ASan) detects out-of-bounds access, use-after-free, and other memory errors in C/C++/ObjC code. Thread Sanitizer (TSan) detects data races in multithreaded code. Undefined Behavior Sanitizer (UBSan) detects undefined behavior, such as signed int overflow. These options are available in Edit Scheme → Run → Diagnostics and work only for Debug builds. Enabling all sanitizers can slow down startup by 2-3 times, so it is recommended to enable them selectively.
A typical practice is to create separate schemes for each environment: Dev, Staging, Production. Each scheme uses the same Build Configuration (Debug for Dev, Release for Production), but different launch arguments: -FIRAnalyticsDebugEnabled, -com.apple.CoreData.SQLDebug 1 for Dev, and their absence for Production. Launch arguments are passed to UserDefaults (ProcessInfo.processInfo.arguments) and are available to read at app startup. This makes it possible to switch the server URL, logging level, and features without changing code.
Shared schemes are stored in <project>.xcworkspace/xcshareddata/xcschemes/ or <project>.xcodeproj/xcshareddata/xcschemes/ and get into the Git repository. All team developers see these schemes in Xcode. Shared schemes are the only way to distribute schemes within a team. If a developer created an important scheme (for example, "Staging Archive") but did not mark it as Shared, the rest of the team will not see it, which leads to confusion: everyone will create their own scheme with their own settings.
Private schemes are stored in xcuserdata/<user>/xcschemes/ and do not get into Git. They are useful for personal configurations: for example, a scheme with all sanitizers enabled for a particular developer. Private schemes should not contain critical settings that the project build depends on — if the developer leaves the project, their private schemes will disappear. Recommendation: make all schemes used in CI/CD and by at least two developers Shared.
Managing schemes is done through Manage Schemes (Product → Scheme → Manage Schemes). The window shows all project schemes, their status (Shared/Private), and +/— buttons for adding/deleting. The Shared checkbox switches the visibility of the scheme for the team. In case of a Git conflict (changes to .xcscheme by two developers), you need to carefully resolve the merge — XML files may contain different target identifiers. It is recommended to add .xcscheme to files locked during merge (git lfs or .gitattributes).
Arguments in Scheme are strings passed to the app at launch (ProcessInfo.processInfo.arguments) and environment variables (ProcessInfo.processInfo.environment). Arguments are used for flags: -AppleLanguages (ru), -AppleLocale ru_RU to simulate the Russian locale, or -FIRDebugEnabled to enable Firebase debugging. Environment variables are used for configuration: API_BASE_URL=http://localhost:3000, LOG_LEVEL=debug.
To manage features (feature flags) in different environments, a combination of Arguments + Build Configuration is used. In the Dev scheme the argument -FeatureFlagNewOnboarding YES is set, and in Production — -FeatureFlagNewOnboarding NO (or the argument is absent). In code the check is: UserDefaults.standard.bool(forKey: "FeatureFlagNewOnboarding"). This approach allows gradually enabling features on staging without changing code and without committing production values.
Important: Scheme arguments and environment variables override values from Info.plist. If API_URL is specified in Info.plist, and in Scheme — API_URL=http://localhost for the Run Action, then when launched from Xcode the value from the Scheme will be used. When launching on a device (not from Xcode) — the value from Info.plist. This is convenient for local development, but you need to remember that Scheme variables do not get into the build — they only apply when launched through Xcode.
import Foundation
struct AppEnvironment {
var apiBaseURL: String {
ProcessInfo.processInfo.environment["API_BASE_URL"]
?? Bundle.main.object(forInfoDictionaryKey: "API_BASE_URL") as? String
?? "https://api.production.com"
}
var isDebugMode: Bool {
ProcessInfo.processInfo.arguments.contains("-DebugModeEnabled")
}
var isNewOnboardingEnabled: Bool {
UserDefaults.standard.bool(forKey: "FeatureFlagNewOnboarding")
}
}
// Usage at startup
let env = AppEnvironment()
NetworkConfig.shared.configure(baseURL: env.apiBaseURL)
In CI/CD (GitHub Actions, Jenkins, GitLab CI), Scheme is used as the main argument of the xcodebuild command. Example: xcodebuild -workspace MyApp.xcworkspace -scheme MyApp -configuration Release -sdk iphoneos archive. The -scheme flag specifies which scheme to use. xcodebuild reads all settings from the .xcscheme file, including build configuration, targets, and build order. This guarantees that CI/CD builds the app with the same parameters as the local IDE.
Shared schemes are critical for CI/CD. If the scheme is not Shared, xcodebuild will not find it in the repository, and the build will fail with a "Scheme not found" error. Rule: before setting up CI/CD, make sure all schemes used are marked Shared. Second rule: in CI/CD, do not use the default scheme (Xcode automatically selects the first scheme) — always pass the scheme name explicitly via the -scheme flag.
For parallel building of several schemes (for example, the app and a watchOS extension), you can run xcodebuild sequentially or in parallel. Modern CI systems allow parallelizing the build of different schemes via a matrix: one job builds the iOS app, the second builds the watchOS extension. This reduces the total build time from 15 to 8 minutes with two parallel agents. At the end, the artifacts are combined into a single .xcarchive using xcodebuild -exportArchive.
#!/bin/bash — CI/CD build with xcodebuild
# 1. Clean and build
xcodebuild clean archive \
-workspace "MyApp.xcworkspace" \
-scheme "MyApp Production" \
-configuration Release \
-sdk iphoneos \
-archivePath "build/MyApp.xcarchive" \
CODE_SIGN_STYLE="Manual" \
PROVISIONING_PROFILE_SPECIFIER="match AppStore"
# 2. Export to IPA
xcodebuild -exportArchive \
-archivePath "build/MyApp.xcarchive" \
-exportPath "build/ipa" \
-exportOptionsPlist "ExportOptions.plist"
Frequently asked questions
Usually 2-3 schemes are enough: Development (Debug), Staging (with arguments for the test server) and Production (Release). For modular libraries — one scheme with testing settings. Do not create too many schemes — each new scheme requires maintenance.
Build Configuration (Debug/Release) is a set of compiler flags defined in .xcconfig. Scheme is a set of actions, each of which references a Build Configuration. The scheme says "use Debug when launching", the configuration defines "Debug means no optimizations, with symbols".
Arguments go into ProcessInfo.processInfo.arguments and UserDefaults (if the argument starts with a dash). Environment variables go into ProcessInfo.processInfo.environment. In code: UserDefaults.standard.bool(forKey: "FeatureFlag") for arguments of the form -FeatureFlag YES.
Yes, in the Build Action you can add several targets. For example, an "App + Watch + Widget" scheme will build all three targets sequentially (if parallelizeBuildables=NO) or in parallel (YES). For archiving the app, only the main target is needed — the rest are built as dependencies.
Swift Package Manager does not replace schemes — the scheme still defines which configuration to use to build SPM dependencies, which tests to run, and how to archive. SPM packages can have their own schemes, which are automatically imported into the project when the package is added.
Summary
We will develop a mobile application turnkey
IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.
Read also