Xcode 26 Breaks AFNetworking Builds: Five Fixes, from Quick Patch to Full Migration
Problem Background
After upgrading to Xcode 26 (including 26.6), many legacy projects still using AFNetworking encounter a compilation error:
error: 'netinet6/in6.h' file not found
Or, in Explicit Modules mode:
error: importing private header <netinet6/in6.h> is not allowed
Root Cause: Xcode 26 enables a stricter Explicit Modules mechanism, prohibiting source code from directly #import-ing Darwin private headers. The IPv6-related type definitions in <netinet6/in6.h> have long been provided indirectly through the public header <netinet/in.h>. This import in AFNetworking is a legacy, redundant inclusion.
The official AFNetworking repository was archived in 2023 and no longer accepts Issues or PRs, so this problem can only be resolved by developers themselves.
This article provides 5 solutions ranging from shallow to deep, covering scenarios from personal projects to team collaboration and CI/CD. Choose based on your actual situation.
Solution 1: Podfile post_install Script Auto-Fix (Recommended First Choice)
Applicable Scenarios
- Team collaboration where you don't want everyone manually modifying Pods source code
- CI/CD pipelines where the fix should apply automatically after every
pod install - You don't want to maintain a private fork and prefer keeping the dependency source clean
Principle
CocoaPods provides a post_install hook that can batch-modify source files in the Pods directory after dependency installation. Use a Ruby script to locate and delete the problematic line of code, achieving a "zero-intrusion" fix.
Complete Code
Add the following at the end of your Podfile:
post_install do |installer|
# Iterate over all Pod targets
installer.pods_project.targets.each do |target|
# Only process AFNetworking (add || conditions for other affected libraries)
next unless target.name == 'AFNetworking'
target.source_build_phase.files.each do |file|
file_path = file.file_ref.real_path.to_s
next unless file_path.end_with?('.m', '.h')
content = File.read(file_path)
if content.include?('#import <netinet6/in6.h>')
new_content = content.gsub(/#import\s+<netinet6/in6.h>\s*\n/, '')
File.write(file_path, new_content)
puts "[Fix] Removed netinet6/in6.h from #{File.basename(file_path)}"
end
end
end
end
Execution
pod install
# Or if Pods already exist
pod install --repo-update
Notes
- The script is idempotent; repeated execution will not cause errors.
- If you upgrade the AFNetworking version or switch Pod sources, you need to re-run
pod installto trigger the script. - It is recommended to commit this script to Git version control to ensure consistency among team members.
Solution 2: Fork Private Repository + Tag Reference
Applicable Scenarios
- You need to make further customizations on top of the fix (e.g., adding a Privacy Manifest, adapting to new APIs)
- Your team has an internal GitLab/GitHub Organization and wants unified control over third-party dependencies
- You want to lock down a stable, fixed version to avoid the uncertainty of post_install scripts
Complete Operation Process
Step 1: Fork and Clone
# Fork AFNetworking/AFNetworking on GitHub/GitLab
git clone https://github.com/your-org-or-username/AFNetworking.git
cd AFNetworking
Step 2: Create a Fix Branch and Modify
git checkout -b fix/xcode26-netinet6
Find AFNetworking/Reachability/AFNetworkReachabilityManager.m and delete:
// ❌ Delete this line
#import <netinet6/in6.h>
Verify that <netinet/in.h> already exists (usually imported at the top of the file), as it contains all necessary IPv6 type definitions.
Step 3 (Strongly Recommended): Add a Privacy Manifest
Since Spring 2024, Apple has required apps submitted to the App Store to declare a privacy manifest. AFNetworking 4.x does not include this file by default. Create PrivacyInfo.xcprivacy in the project root directory:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyTracking</key>
<false/>
<key>NSPrivacyTrackingDomains</key>
<array/>
<key>NSPrivacyCollectedDataTypes</key>
<array/>
<key>NSPrivacyAccessedAPITypes</key>
<array/>
</dict>
</plist>
Confirm that AFNetworking.podspec contains a resource file wildcard (4.x includes *.xcprivacy by default; generally no changes are needed).
Step 4: Commit, Tag, and Push
git add .
git commit -m "fix: remove deprecated netinet6/in6.h & add privacy manifest"
git push origin fix/xcode26-netinet6
# Tag based on the current HEAD with a semantic version
git tag 4.0.1-xcode26-fix
git push origin 4.0.1-xcode26-fix
Step 5: Reference in Your Project
# Podfile
pod 'AFNetworking', :git => 'https://github.com/your-org-or-username/AFNetworking.git', :tag => '4.0.1-xcode26-fix'
pod cache clean AFNetworking --all # Clear old cache
pod install
Team Collaboration Suggestions
- Place the forked repository under your team's Organization, not a personal account.
- Note the modifications and the corresponding upstream commit hash in the README for future traceability.
- Set up Branch Protection to prevent accidental deletion of the fix branch.
Solution 3: Local Patch File + CocoaPods Patch Plugin
Applicable Scenarios
- You don't want to maintain a full fork or write Ruby scripts.
- The fix is very small (just deleting one line), and you want to manage it lightly as a patch.
- You need to reuse the same fix across multiple projects.
Prerequisites
gem install cocoapods-patch
Operation Steps
Step 1: Generate the Patch File
After manually modifying the source file in Pods once, execute the following in the project root directory:
pod patch create AFNetworking
After interactive confirmation, a patches/AFNetworking+4.0.1.patch file will be generated in your project.
Step 2: Declare in Podfile
plugin 'cocoapods-patch'
pod 'AFNetworking', '~> 4.0'
Afterwards, the plugin will automatically apply the patch every time pod install is run.
Advantages
- The patch file is small and can be committed directly to the project's Git repository.
- It is more declarative and readable than a post_install script.
- When upgrading AFNetworking minor versions, the patch might still be compatible.
Solution 4: Bypass at the Xcode Build Settings Level (Temporary Emergency Fix)
Applicable Scenarios
- An urgent build is needed, and there's no time to modify the Podfile or fork.
- Only needed for a local temporary compilation pass, not for CI or team sharing.
Operation Method
In Xcode, select Pods → AFNetworking Target → Build Settings, search for Explicit Modules, and set it to NO.
Or, disable it for a specific Pod in the Podfile:
post_install do |installer|
installer.pods_project.targets.each do |target|
if target.name == 'AFNetworking'
target.build_configurations.each do |config|
config.build_settings['CLANG_ENABLE_EXPLICIT_MODULES'] = 'NO'
end
end
end
end
⚠️ Risk Warning
- This only masks the problem, not truly fixes it.
- Disabling Explicit Modules increases compilation time and reduces modular safety.
- Apple may completely remove this switch in a future version.
- Only for temporary emergencies; not recommended as a long-term solution.
Solution 5: Migrate to a Modern Networking Framework (Fundamental Solution)
Why You Should Migrate
| Dimension | AFNetworking | Modern Alternatives |
|---|---|---|
| Maintenance Status | Archived in 2023, no longer updated | Actively maintained |
| Language | Objective-C | Swift / ObjC compatible |
| Privacy Compliance | No built-in Privacy Manifest | Native support |
| Async/Await | Not supported | URLSession / Alamofire native support |
| Explicit Modules | Multiple private header references | Fully compliant |
| Security Audit | Previously injected by malicious software | Active community, fast security response |
Three Migration Paths
Path A: Native NSURLSession Wrapper (Lightest Weight)
Suitable for projects with a thin network layer and simple request types. Directly wrap a lightweight NetworkClient using Apple's native API with zero external dependencies:
final class NetworkClient {
static let shared = NetworkClient()
private let session: URLSession
private init() {
let config = URLSessionConfiguration.default
config.timeoutIntervalForRequest = 30
self.session = URLSession(configuration: config)
}
func request(_ url: URL) async throws -> Data {
let (data, response) = try await session.data(from: url)
guard let http = response as? HTTPURLResponse,
200...299 ~= http.statusCode else {
throw URLError(.badServerResponse)
}
return data
}
}
Path B: Alamofire (Preferred for Swift Projects)
The "spiritual successor" to AFNetworking, with a consistent API design style, offering the lowest migration cost for Swift projects:
# SPM or Podfile
pod 'Alamofire', '~> 5.9'
import Alamofire
AF.request("https://api.example.com/data")
.validate()
.responseDecodable(of: MyModel.self) { response in
switch response.result {
case .success(let model): print(model)
case .failure(let error): print(error)
}
}
Path C: Moya + Alamofire (Recommended for Large Projects)
Provides an API endpoint abstraction layer on top of Alamofire, suitable for projects with many interfaces requiring modular management:
enum UserAPI {
case profile(id: Int)
case update(name: String)
}
extension UserAPI: TargetType {
var baseURL: URL { URL(string: "https://api.example.com")! }
var path: String { ... }
var method: Moya.Method { ... }
var task: Task { ... }
var headers: [String: String]? { ... }
}
Incremental Migration Strategy
You don't need to replace all requests at once. The recommended approach:
- Use the new framework for all new interfaces.
- Gradually replace old AFNetworking calls module by module.
- Keep AFNetworking as a fallback until coverage reaches the target.
- Finally, remove the AFNetworking dependency.
Solution Selection Decision Tree
Compilation error netinet6/in6.h
│
├── Urgent build, need a fix in 10 minutes? ──→ Solution 4 (Temporarily disable Explicit Modules)
│
├── Team project, want automation without modifying source? ──→ Solution 1 (post_install script) ✅ Recommended
│
├── Need customization + Privacy Manifest + long-term maintenance? ──→ Solution 2 (Fork + Tag)
│
├── Fix is minimal, want to manage with a patch? ──→ Solution 3 (cocoapods-patch)
│
└── Project has a long lifecycle ahead? ──→ Solution 5 (Migration) 🎯 Ultimate Solution
Common Pitfalls FAQ
Q1: Will deleting netinet6/in6.h affect IPv6 functionality?
No. <netinet/in.h> already includes all necessary definitions like sockaddr_in6 and IN6ADDR_ANY_INIT. AFNetworking itself does not directly use any symbols unique to netinet6/in6.h.
Q2: Besides AFNetworking, which other libraries have the same issue?
According to community feedback, ZFPlayer, YYText, and some older versions of WCDB also have similar private header references. The post_install script in Solution 1 can be extended into a general fix by adding the corresponding Pod names to the next unless condition.
Q3: Does the post_install script work normally on M1/M2 Macs?
Yes. The script operates on plain text files and is architecture-independent. However, note that CocoaPods 1.15+ has slightly adjusted the installer.pods_project API; it is recommended to use CocoaPods ≥ 1.15.2.
Q4: What if the upstream updates after forking? AFNetworking is archived and will not receive upstream updates. If you fork other libraries that are still maintained, it is recommended to periodically rebase the upstream main branch and submit your fix back upstream as a PR.
Q5: What happens if I don't add the Privacy Manifest? Since May 2024, App Store Connect issues warnings for third-party SDKs missing a privacy manifest. Starting in 2025, missing key API declarations may lead to app review rejection. It is recommended to complete this alongside fixing the compilation issue.
Summary
Xcode 26's strict validation of private headers is essentially pushing the ecosystem toward a safer, more modular direction. For archived historical libraries like AFNetworking, use scripts or forks to extend their life in the short term, but plan for migration in the long term. Technical debt doesn't disappear; it just erupts more violently with the next Xcode upgrade.
Choose the solution that best fits your current stage and start taking action now.
Top 1 from juejin.cn, machine-translated. The original thread is authoritative.
Very thorough compilation, the scenario breakdown for the five solutions is clear. We've also used post_install to batch-process other legacy issues in Pods before; it's idempotent and works in CI, making it the top choice for team scenarios. Two additions: first, when forking for solution two, it's crucial to put the repo under the team organization and note the corresponding upstream commit in the README—this saves a lot of backtracking effort during future upgrades, and it's great that the article mentioned this. Second, we used solution four once in an emergency; the compilation time did increase and we lost module validation, so we switched back to solution one right after shipping the build. Solution five's migration is a permanent fix, but the regression testing cost for legacy projects is significant. Our approach is to first fix the build with solution one or three, then schedule the migration as technical debt to be digested gradually. Thanks for sharing, bookmarked!