Build and Tooling
|
This section documents modern Objective-C as implemented by Apple Clang in the current Xcode release — Objective-C 2.0 plus ARC, literals and subscripting, This content was generated with the assistance of AI and should be verified against developer.apple.com before being relied on in production. This section’s bibliography lists the reference material consulted while preparing these pages. |
Objective-C’s toolchain is Clang plus the platform’s frameworks. On Apple platforms that means Xcode, which wraps compilation, linking, analysis, profiling and debugging in one place; elsewhere it means Clang or GCC plus GNUstep for a Foundation implementation.
Xcode and xcodebuild
Xcode is the reference environment. Its command-line counterpart, xcodebuild, is what CI systems use:
# Build a scheme
xcodebuild -project MyApp.xcodeproj -scheme MyApp -configuration Debug build
# A workspace (required when CocoaPods is in play)
xcodebuild -workspace MyApp.xcworkspace -scheme MyApp build
# Run the tests
xcodebuild test -workspace MyApp.xcworkspace -scheme MyApp \
-destination 'platform=iOS Simulator,name=iPhone 15'
# Static analysis
xcodebuild analyze -workspace MyApp.xcworkspace -scheme MyApp
# Clean, archive and export
xcodebuild clean
xcodebuild archive -scheme MyApp -archivePath ./build/MyApp.xcarchive
xcodebuild -exportArchive -archivePath ./build/MyApp.xcarchive \
-exportOptionsPlist ExportOptions.plist -exportPath ./build
# Inspect available destinations and settings
xcodebuild -showdestinations -scheme MyApp
xcodebuild -showBuildSettings -scheme MyApp
Two related tools: xcrun runs any tool from the active toolchain (xcrun clang …, xcrun simctl …), and
xcode-select --install installs the Command Line Tools without the full Xcode application.
For direct compilation of a small program:
clang -fobjc-arc -fmodules -Wall -Wextra \
-framework Foundation \
main.m Person.m -o myapp
Compiler Flags
| Flag | Effect |
|---|---|
|
Enable ARC. Per-file; |
|
Enable Clang modules, so |
|
Enable |
|
Link a framework. |
|
Load every Objective-C class and category from static libraries. Required when a static library contains categories, or their methods vanish at run time. |
|
Heavier variants of the same idea. |
|
Turn on the useful warnings. Non-negotiable. |
|
Treat warnings as errors. Excellent in CI; adopt incrementally on an existing codebase. |
|
Silence one specific warning, e.g. |
|
Optimisation: none (debug), speed, size. |
|
Emit debug symbols. |
|
Emit ARC cleanup on exception paths. Off by default; costs performance. |
|
Compile as Objective-C++ (implied by the |
|
Enable a sanitizer. |
Warnings worth enabling beyond -Wall -Wextra: -Wundeclared-selector (catches @selector typos),
-Wdeprecated-declarations, -Wdocumentation, and -Wobjc-missing-property-synthesis.
Building Elsewhere
Linux with GNUstep
# Install (Debian/Ubuntu)
sudo apt install gnustep gnustep-devel gobjc clang
# Compile and link, letting gnustep-config supply the flags
clang `gnustep-config --objc-flags` hello.m -o hello `gnustep-config --base-libs`
# With GCC instead
gcc `gnustep-config --objc-flags` hello.m -o hello `gnustep-config --base-libs`
gnustep-config --objc-flags emits include paths and runtime-selection flags; --base-libs emits the linker
flags for libgnustep-base, GNUstep’s Foundation implementation.
What differs from Apple’s toolchain, in practice:
-
ARC works with Clang and the modern
libobjc2runtime, but not with GCC’s GNU runtime — legacy GNUstep code is usually MRR. -
Clang modules (
@import) are generally unavailable; use#import. -
Only Foundation-level APIs exist. UIKit is absent entirely; AppKit has a partial GNUstep counterpart.
-
Blocks require
-fblocksand the BlocksRuntime library.
GNUstep is the practical route for command-line tools and portable model code, not for UI.
The Clang Static Analyzer
The analyzer explores execution paths without running the program, and finds memory, nullability and logic errors that neither the compiler’s warnings nor your tests will catch.
xcodebuild analyze -workspace MyApp.xcworkspace -scheme MyApp
clang --analyze -fobjc-arc MyClass.m
In Xcode it is ⇧⌘B (Product ▸ Analyze), and Analyze During Build runs it on every build. What it finds:
-
Leaked and over-released objects, including at the Core Foundation boundary.
-
Mismatched bridging casts.
-
Dereferencing a value that can be
nilon some path. -
Values stored but never read, and logic that can never execute.
-
API misuse it has been taught about.
Run it routinely — it is the cheapest quality tool in the Objective-C toolbox, and it complains far less often than a linter would.
Sanitizers
Sanitizers instrument the binary and report errors at the moment they happen, with a stack trace. Enable them in the scheme’s Diagnostics tab, or on the command line.
| Sanitizer | Flag |
|---|---|
Finds |
Address |
|
Use-after-free, buffer overflow, use-after-return, double-free. ~2× slower. |
Thread |
|
Data races, including ones your tests do not reproduce. 5-15× slower. |
Undefined Behavior |
|
Signed overflow, misaligned or null pointers, invalid casts. |
Main Thread Checker |
(Xcode scheme option) |
UIKit/AppKit calls made off the main thread. |
Malloc Scribble / Guard Edges |
environment variables |
Heap corruption. |
Address and Thread Sanitizer cannot be enabled together. The practical routine is to run the test suite under ASan regularly and under TSan periodically — TSan in particular finds races that will otherwise surface as irreproducible field crashes. Leave the Main Thread Checker on always; it costs nothing noticeable.
Instruments
Instruments is the profiler, driven by templates:
| Template | Use |
|---|---|
Leaks |
Memory with no remaining references. Run it, exercise the app, look for the growth. |
Allocations |
Every allocation, with Mark Generation to isolate what a given operation leaves behind — the best tool for finding retain cycles. |
Zombies |
Messages sent to deallocated objects. Turns a corrupted-memory crash into a precise report. |
Time Profiler |
Where CPU time goes, by stack frame. |
Network / File Activity |
I/O behaviour. |
Energy Log |
Battery impact of your QoS choices. |
The retain-cycle routine with Allocations is worth memorising: mark a generation, perform the operation you suspect, return the app to its starting state, mark again — anything still alive in the newer generation is a candidate.
For a cycle you can already reproduce, the Memory Graph Debugger in Xcode (the graph icon in the debug bar)
is faster: it draws the live object graph and annotates leaked objects with a purple !.
# Command-line profiling
xcrun xctrace record --template 'Time Profiler' --launch -- ./myapp
Debugging with lldb
(lldb) po myObject # print the object's -debugDescription
(lldb) p myInteger # print a scalar / expression
(lldb) p (int)[array count] # cast when lldb cannot infer the return type
(lldb) expr self.name = @"Ada" # evaluate an expression, with side effects
(lldb) bt # backtrace for this thread
(lldb) bt all # every thread
(lldb) frame variable # all locals in the current frame
(lldb) up / down # move through frames
(lldb) b MyClass.m:42 # breakpoint by file and line
(lldb) b -[MyClass doThing:] # breakpoint on a method
(lldb) breakpoint set -E objc # break on every Objective-C exception THROWN
(lldb) c / n / s / finish # continue / step over / step in / step out
(lldb) image lookup -a 0x10a2b3c4 # symbolicate an address
The exception breakpoint deserves its own mention. By default, an uncaught exception surfaces deep inside
objc_exception_throw with a useless stack. Adding an "All Objective-C Exceptions" breakpoint (the + at the
bottom of Xcode’s breakpoint navigator, or breakpoint set -E objc) stops execution at the throwing line
instead. Set it once per project and leave it.
Two more habits: po uses debugDescription, so overriding that on your model classes pays for itself
immediately (see Classes and Objects); and a
symbolic breakpoint on -[UIView setFrame:] or similar is often quicker than hunting for a caller.
Dependency Managers
| Tool | Notes |
|---|---|
Swift Package Manager |
Apple’s own, integrated into Xcode, and it supports Objective-C targets. The default choice for new work. |
CocoaPods |
The long-established Objective-C dependency manager, with by far the largest catalogue of Objective-C
libraries. Generates an |
Carthage |
Builds dependencies into frameworks and leaves integration to you. Less common now. |
# Podfile
platform :ios, '15.0'
use_frameworks!
target 'MyApp' do
pod 'AFNetworking', '~> 4.0'
pod 'OCMock', '~> 3.9'
end
pod install # install and generate MyApp.xcworkspace -- open THIS, not the .xcodeproj
pod update
pod outdated
A Swift package with an Objective-C target:
// Package.swift
targets: [
.target(
name: "MyObjCLibrary",
path: "Sources/MyObjCLibrary",
publicHeadersPath: "include" // the directory clients import from
)
]
The include directory convention matters: SwiftPM exposes exactly those headers, so the public/private split
of
Modules, Frameworks and
Code Organization maps onto directory layout here.
Publishing a Library
Unlike Kotlin’s pointer to Java’s Maven Central section (an already-documented registry, so nothing more than a cross-reference is needed), CocoaPods has no canonical page of its own anywhere on this site — so it gets a full walkthrough here, while the SPM-registry angle below still just cross-references Swift’s page rather than repeating it.
CocoaPods is the long-established path for distributing an Objective-C library, and remains the option with the largest catalogue of existing Objective-C consumers. Publishing starts with a one-time CocoaPods Trunk account, registered by email:
pod trunk register your.email@example.com 'Your Name'
The account confirms via a link sent to that email, after which the machine holds a session token in
~/.netrc used by every subsequent pod trunk push. The library itself is described by a .podspec at its
root — a minimal one:
# MyObjCLibrary.podspec
Pod::Spec.new do |s|
s.name = "MyObjCLibrary"
s.version = "1.0.0"
s.source = { :git => "https://github.com/example/MyObjCLibrary.git", :tag => s.version.to_s }
s.source_files = "Sources/**/*.{h,m}"
s.platform = :ios, "15.0"
end
pod spec lint validates the podspec against a real build before anything is pushed — catching a missing file
pattern or an unresolved dependency locally rather than after publishing:
pod spec lint MyObjCLibrary.podspec
pod trunk push MyObjCLibrary.podspec
An Objective-C target can also be distributed as a Swift package rather than (or alongside) a CocoaPod — see Swift Package Manager for the SPM-registry angle covered there, which applies to an Objective-C target the same way it does to a Swift one.
A tag-gated GitHub Actions release workflow, authenticated with a CocoaPods Trunk token stored as a repository secret:
name: build
on:
push:
branches: [ main ]
tags: [ 'v*' ]
pull_request:
jobs:
build:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- name: Install CocoaPods
run: gem install cocoapods
- name: Lint podspec
run: pod spec lint --allow-warnings
- name: Publish to CocoaPods Trunk
if: startsWith(github.ref, 'refs/tags/v')
env:
COCOAPODS_TRUNK_TOKEN: ${{ secrets.COCOAPODS_TRUNK_TOKEN }}
run: pod trunk push --allow-warnings
COCOAPODS_TRUNK_TOKEN is read directly from the environment by pod trunk push, per CocoaPods' own CI
documentation — no ~/.netrc file needs to be checked in or generated in the workflow.
clang-format
Automated formatting, so style stops being a review topic:
# .clang-format
BasedOnStyle: LLVM
Language: ObjC
ColumnLimit: 120
ObjCBlockIndentWidth: 4
ObjCSpaceAfterProperty: true
ObjCSpaceBeforeProtocolList: true
IndentWidth: 4
AllowShortFunctionsOnASingleLine: false
clang-format -i MyClass.m # format one file in place
find . -name '*.m' -o -name '*.h' | xargs clang-format -i
clang-format --dry-run --Werror MyClass.m # check only -- good for CI
Add the CI check before reformatting an existing codebase wholesale, so the diff stays reviewable.
For linting proper, oclint offers rule-based static analysis beyond what the Clang analyzer does, though the
analyzer covers most of the value.
See Also
-
Getting Started — the minimal build commands.
-
Automatic Reference Counting — diagnosing leaks in detail.
-
Testing —
xcodebuild testand XCTest. -
Modules, Frameworks and Code Organization —
-ObjC, modules and header visibility.