Mobile Development 18 min read

Integrating Swift into an Existing Objective‑C iOS App: Challenges, Solutions, and Best Practices

This article details Ctrip's experience of adopting Swift for iOS development, covering the language's evolution, statistical adoption, static‑library integration, ObjC‑Swift mixed compilation, module stability with swiftinterface files, CI pipeline adjustments, and strategies for seamless ObjC‑Swift interoperation.

Ctrip Technology
Ctrip Technology
Ctrip Technology
Integrating Swift into an Existing Objective‑C iOS App: Challenges, Solutions, and Best Practices

Swift, first released in 2014, struggled with ABI instability and API incompatibility until Swift 5.0 introduced a stable ABI, allowing apps to run without embedding the Swift runtime. Apple then accelerated Swift's ecosystem with frameworks such as SwiftUI, Combine, and RealityKit, signaling a strong commitment to the language.

Usage statistics show that 78% of US App Store top‑1000 apps now use Swift, and 26 of the top‑100 Chinese apps have adopted it, surpassing React Native and Flutter. This rapid adoption motivated Ctrip's hotel‑technology team to evaluate Swift for their main iOS app.

1. Starting Point – The team chose to migrate low‑level utility classes to Swift and created a dedicated static Swift library to minimize architectural impact. Xcode 9 already supports building static Swift libraries, so no major project changes were required.

2. ObjC & Swift Mixed Compilation – Initial attempts to run the app crashed with dyld: Library not loaded: @rpath/libswiftCore.dylib . The fix involved adding /usr/lib/swift to the Runpath Search Paths and setting Always Embed Swift Standard Libraries to Yes :

# Add Runpath Search Path
/usr/lib/swift

# Enable embedding of Swift runtime
Always Embed Swift Standard Libraries = Yes

Because CI builds only search source directories, the generated xxx‑Swift.h header (produced in the build folder) was not found. The team solved this by copying the header back to the source tree after each build using a script:

mkdir -p ${include_dir}
cp ${generated_header_file} ${include_dir}

# Remove compiler version comment from header
sed -i "" "s/^\/\/ Generated by Apple.*$/\/\/ Generated byApple/g" ${generated_header_file}

# Copy header into project source directory if needed
header_file_in_proj=${SRCROOT}/${PROJECT}-Swift.h
needs_copy=true
if [ -f "${header_file_in_proj}" ]; then
    new_content=$(cat ${generated_header_file})
    old_content=$(cat ${header_file_in_proj})
    if [ "${new_content}" = "${old_content}" ]; then
        needs_copy=false
    fi
fi
if [ "${needs_copy}" = true ]; then
    cp ${generated_header_file} ${header_file_in_proj}
fi

With the header available, ObjC code could import Swift symbols via:

#import

3. Swift → Swift Inter‑module Calls – Simple Swift code can call another Swift library, but CI builds failed with Module compiled with Swift 5.1 cannot be imported by the Swift 5.1.2 compiler . The issue stemmed from the compiled swiftmodule being tied to a specific compiler version.

Apple introduced module stability via swiftinterface files, which are textual representations of the public API and are compiler‑version agnostic. Enabling Build Libraries for Distribution in the target settings generates both swiftmodule and swiftinterface files.

Example of a generated swiftinterface file:

//swift-interface-format-version: 1.0
//swift-compiler-version: Apple Swift version 5.1 (swiftlang-1100.0.270.13clang-1100.0.33.7)
//swift-module-flags: -target x86_64-apple-ios13.0-simulator -enable-objc-interop -enable-library-evolution -swift-version 5 -enforce-exclusivity=checked -Onone-module-name SwiftLibB
import Foundation
import Swift
@objc @objcMembers public class SwiftLibB : NSObject {
  @objc public func sayHello(name: Swift.String)
  @objc override dynamic public init()
  @objc deinit
}

Distributing the swiftinterface alongside the static library ensures binary compatibility across compiler versions.

4. Swift → ObjC Integration – When Swift needed to be called from ObjC, the team could not use a bridging header because the project relies on static libraries. Instead, they added the generated Swift headers to the ObjC umbrella header, allowing Swift symbols to be visible to ObjC code.

5. Modulemap for Static Libraries – To improve compile‑time performance and simplify header inclusion, a module.modulemap file can be created for static libraries, declaring an umbrella header that aggregates all public headers.

6. Summary – By using a static Swift library, embedding the Swift runtime, copying generated headers, and leveraging swiftinterface for module stability, Ctrip successfully integrated Swift into their large ObjC codebase, enabling seamless bidirectional calls and CI compatibility while benefiting from Swift's modern language features.

iOSSwiftObjective-Cstatic libraryCI IntegrationModule StabilityApp Thinning
Ctrip Technology
Written by

Ctrip Technology

Official Ctrip Technology account, sharing and discussing growth.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.