The deadline is real, but it isn't what people think
CocoaPods trunk goes permanently read-only on December 2, 2026, with a test run of read-only mode on November 1–7. After that the central registry stops accepting new pod versions and new podspecs. Apps depending on existing pod versions keep building and running.
So nothing breaks on December 3rd. What stops is new releases, and the vendors moved first: Firebase stops publishing new Apple SDK versions to CocoaPods in October 2026, and Google said it would end CocoaPods support for its iOS SDKs after Q2 2026 in favour of Swift Package Manager.
The failure mode isn't a build error. It's a security patch you can't take, six months from now, in a transitive native dependency you didn't know you had.
What React Native actually ships today
React Native 0.87 added experimental Swift Package Manager support as an alternative to CocoaPods on iOS. It's opt-in and additive, CocoaPods remains the default supported path, and the SwiftPM path consumes the same prebuilt XCFrameworks React Native already publishes. No Ruby, no Bundler, no CocoaPods — just Xcode.
cd ios
npx react-native spm -- deintegrate
It injects Swift package references into your existing
.xcodeproj rather than replacing it, so signing,
capabilities and build phases stay untouched, and
npx react-native spm deinit reverses it exactly. After a
fresh clone and in CI you run npx react-native spm once
before building — the analog of pod install. Commands,
flags and generated layout may still change, and it's explicitly not for
production yet.
The load-bearing sentence is the next one: a community library must ship
a Package.swift, and if it doesn't,
npx react-native spm scaffold generates one from the
podspec.
That scaffold command is the whole ballgame. So we measured how often it works.
How we measured it
Our script doesn't reimplement podspec parsing. It loads React Native's
own scripts/spm/read-podspec.js out of the target app's
node_modules and uses that — the module that evaluates a
podspec's Ruby DSL through pod ipc spec, patches out
Podfile-only helpers like
install_modules_dependencies(s) first so evaluation doesn't
fail, falls back to a regex parser when CocoaPods isn't installed, and
flattens subspecs. Roughly 800 lines of accumulated edge cases.
Reimplementing that would drift from what
react-native spm scaffold actually does, and a classifier
that disagrees with the tool it's predicting is worthless. The
mixed-language gate in particular mirrors RN's own scaffolder check
exactly, verified against 0.87.1 source.
Four buckets:
-
ready — ships its own
Package.swift. A manifest carrying RN's auto-scaffolder marker doesn't count; that's a contaminatednode_modules, not a maintainer decision. -
scaffoldable — no manifest, single-language sources,
no unresolvable pod dependency.
scaffoldshould produce something that works. -
manual —
scaffoldfails closed. A human writes the manifest. - blocked — depends on a pod with no known SwiftPM distribution. No manifest fixes this.
| Status | Count | Share |
|---|---|---|
| scaffoldable | 193 | 68% |
| manual | 82 | 29% |
| ready | 8 | 3% |
| blocked | 0 | 0% |
| unclassifiable | 17 | 6% of the run |
283 classifiable libraries, from a 300-library run plus one spotlight. The 17 errors are excluded from the percentages rather than quietly bucketed.
Two numbers are worth sitting with.
Only 8 of 283 already ship a manifest. Three percent, a
month after npx react-native spm landed in a stable
release. The ecosystem has essentially not started.
The 29% holds when weighted by usage. The comfortable story would be that abandoned packages drag the average down. The data says otherwise: the manual bucket is 47 million of the sample's 165.5 million weekly downloads, or 28%. The hard libraries are the ones people actually install.
One cause, not a hundred
Here's what surprised us. The manual bucket isn't a long tail of exotic podspec features. It's one check:
if (report.hasSwift && report.hasClang) {
report.status = 'manual';
// 'skipped-mixed-language'
}
SwiftPM cannot compile Swift and Objective-C/C++ in a single target the
way CocoaPods does. Every library whose source_files glob
matches both a .swift and an
.m/.mm/.c/.cpp is
unscaffoldable, full stop. All 82.
The fix is a real refactor of the library's native layout: split the
Swift shim into its own target and have it import the Objective-C half
under #if SWIFT_PACKAGE. No generator does that for you,
because it's a judgement call about module boundaries.
This isn't theoretical or marginal. RN 0.87's SwiftPM path is not yet compatible with the mixed-language native target Nitro Modules requires, so Nitro-based libraries stay on CocoaPods for now.
A single architectural mismatch, mechanically detectable, accounting for 28% of installs. That's unusually tractable as ecosystem problems go — and it means a registry of hand-written two-target manifests would move the whole ecosystem at once.
What we found in the podspecs along the way
Scanning 300 real packages surfaces things you can't find by reading the RFC.
An upstream bug in React Native's own podspec reader.
@react-native-clipboard/clipboard has no top-level
s.source_files. It declares
s.ios.source_files, s.osx.source_files and
s.visionos.source_files — CocoaPods' per-platform attribute
syntax, which pod ipc spec resolves by nesting each value
under its platform key. React Native's flattener only ever reads the top
level, so on both 0.87-stable and main the
model comes back with zero source files for a perfectly valid podspec.
We ran React Native 0.87.1's own scaffold code against the published
clipboard 1.16.3 to see what happens next. It doesn't fail closed. It
emits a target with path: "." and no sources,
no publicHeadersPath, no header search paths — and with no
source list, SwiftPM compiles whatever it finds in the package root. For
clipboard that's the iOS implementation, plus the macOS one — a second
@implementation RNCClipboard, written against AppKit's
NSPasteboard — plus four Windows C++ files whose
precompiled header includes <windows.h>, a header the
iOS SDK doesn't have.
Expo modules hide their podspecs. They nest them under
ios/, named nothing like the npm package —
expo-constants ships ios/EXConstants.podspec.
A root-only search finds nothing and reports a false negative.
Podspecs contain globs that Ruby accepts and nothing else
does.
react-native-vision-camera writes
"ios/**/*.{swift}" — a single-item brace group.
Dir.glob handles it; fast-glob treats
{swift} as four literal characters and matches nothing.
That silently zeroed out a real 175-file match during an end-to-end run,
which would have flipped a manual verdict to a bogus
error.
What this means for you
If you ship an app. Upgrade to 0.87 and try the SwiftPM
path now — on a branch. It costs less than it sounds:
npx react-native spm adds packages to your existing
.xcodeproj rather than replacing it, and
npx react-native spm deinit reverses it exactly, so a
failed afternoon costs you nothing. What you get back is finding out
which of your dependencies break while there's still time to do
something about it. The clipboard bug above only turned up because we
ran React Native's own scaffold code against real packages; your
dependency tree has its own version of that waiting. Run the scan first
so you know how many of your libraries are mixed-language, then try the
switch and file what breaks. Every issue reported in September gets
fixed before December instead of discovered after it. Just keep it off
your release branch for now — the core team is explicit that the SwiftPM
path isn't production-ready yet, and 0.87.1 alone carried fixes for
Hermes bytecode mismatches in Release builds.
If you maintain a library. Run the check yourself: does
your source_files glob match both .swift and
.m/.mm? If not, you are one
npx react-native spm scaffold away from done — ship the
manifest, list it in your package's files array, and note
that the config now lives in swiftpmConfig in
package.json (the spm block in
react-native.config.js is deprecated). If yes, you owe your
users a target split, and it's better done deliberately than under
deadline. Either way it's additive: CocoaPods integration is unchanged,
there's no flag day, no coordination problem.
For React Native. Removing Ruby from the iOS toolchain
is the biggest simplification the platform has had in years, and it
lines up with precompiled binaries by default, the New Architecture as
standard, and Hermes V1. Expo SDK 58 has restructured its iOS sources so
Swift, Objective-C and C++ compile as separate SwiftPM targets, with an
autolinking plugin for npx react-native spm in review —
which is precisely the target split every mixed-language library now
needs, done once at the framework level. The core is ready. The
ecosystem is at 3%.
Caveats, honestly
A manual verdict can be wrong for binary
distributions.
expo-constants's podspec conditionally skips compiling
sources entirely in favour of a prebuilt .xcframework when
one is present. A fresh scan never has that binary on disk, so it always
evaluates the other branch — and CocoaPods only reports the taken
branch, so the resolved spec doesn't even mention
vendored_frameworks. We flag any podspec whose
text mentions one, but a library shipping a binary framework
sidesteps the single-target-language limit entirely. Some share of the
82 may be fine in practice.
Some of the 17 errors are our problem, not the libraries'. At least one is the RN reader bug above.
This predicts whether a manifest can be generated, not whether the resulting build links, runs and archives. That's a harder question and a different tool.